ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Boot - Jackson 라이브러리를 이용한 데이터 바인딩(1)
    Framework & Library/Spring Boot 2023. 2. 8. 14:52

    Jackson 라이브러리를 이용한 데이터 바인딩(1)

    Jackson 라이브러리란?

    오늘날의 거의 모든 웹 서비스는 JSON 형태로 데이터를 사용하고 생성한다. JSON 데이터를 애플리케이션 내의 POJO 객체로 변환하고 POJO 객체를 다시 JSON 형태로 변환해서 내보내는 역할이 필요한데, Spring에서는 보편적으로 이러한 역할을 Jackson 라이브러리가 도맡아 처리한다.

    이처럼, Jackson은 JSON 처리를 위한 다목적 고성능 Java 라이브러리이다. Java 객체를 JSON 형태로 또는 JSON을 Java 객체로 변환하는 데 사용되는 바인딩 기능을 제공해 준다.

    이번 게시글에서는 어떻게 Jackson 라이브러리가 JSON 데이터를 POJO 객체로 변환하는지를 알아보겠다.

     

    ※ POJO : 특정 기술에 종속되지 않는 순수한 자바 객체를 의미한다.

     

    1. public 필드만 있는 경우

    Jackson 라이브러리는 기본적으로 필드명을 기준으로 데이터를 매핑한다.

     

    {
        "name" : "홍길동",
        "address" : "서울특별시"
    }

    예를 들어 위와 같은 JSON 데이터를 POJO 객체로 변환할 때, 변환할 클래스 내 정의된 멤버변수인 name을 참조하게 된다.

     

    public class Member {
        public String name;
        public String address;
    }

    즉, 멤버변수의 이름(name, address)이 JSON 데이터의 key와 매핑된다고 이해하면 된다.

     

    중요한 것은 멤버변수명을 기준으로 매핑 작업이 이루어질 때는 멤버변수의 접근제어자가 public이어야 한다는 것이다. public 이외의 다른 접근제어자를 명시하게 되면, JSON 데이터와 매핑이 안되기 때문에, 주의해야 한다.

     

    하지만, 애플리케이션에 적용되는 DTO 클래스는 기본적으로 private 멤버변수를 가지고 getter() 메서드를 통해 해당 필드에 접근하도록 되어있다. 즉, 위의 public 멤버변수의 형태는 거의 쓰이지 않는다는 얘기다.

     

    2. private 필드와 getter() 메서드로 이루어진 경우

    public 이외의 접근제어자(protected, private) 멤버변수에 대하서는 getter() 메서드를 작성함으로써, JSON 데이터와 매핑할 수 있다.

     

    public class Member {
        private String name;
        private String address;
        
        public String getName() {
            return this.name;
        }
        
        public String getAddress() {
            return this.address;
        }
    }

    위 코드와 같이 getter() 메서드를 작성하면, 해당 getter() 메서드의 이름을 통해 멤버변수에 데이터를 매핑한다.

     

    예를 들어, {"name" :  "홍길동"} JSON 데이터를 객체로 바인딩했을 때, POJO 객체의 getName() 메서드에 의해 name 멤버번수에는 "홍길동"이라는 값이 매핑된다. 즉, getXxxx()  메서드는 "Xxxx" 필드와 매핑되는 것이다.

     

    위와 같은 방식은 언제나 private 멤버변수와 getter() 메서드만 있는 상횡에 해당되는 내용이다.

     

    3. 필드명과 getter() 메서드의 이름이 불일치하는 경우

    앞선 예제에서 확인할 수 있었듯이 필드명과 getter() 메서드명은 일치해야 한다. 이번에는 필드명과 getter() 메서드명이 불일치하는 경우에 대해서 알아보겠다.

     

    public class Member {
        private String name;
        
        public String getMemberName() {
            return name;
        }
    }

    위와 같은 POJO 객체 내에 name이라는 필드와 getMemberName()이라는 메서드가 있다고 가정해 보겠다.

     

    @Test
    @DisplayName("필드명과 getter() 메서드의 이름이 불일치 하는 경우")
    public void test01() throws JsonProcessingException, JSONException{
        String memberName = "홍길동";
        
        json.put(MEMBER_NAME, memberName);
        
        JsonProcessingException exception = assertThrows(JsonProcessingException.class, () -> {
            mapper.readValue(json.toString(), Member.class);   // JSON 데이터 -> POJO 객체
        }, "JSON 파싱관련 에러가 발생하지 않았습니다.");
        
        logger.info(exception.getMessage());
    }

     

    JSON 형태의 데이터가 POJO 객체로 정상적으로 매핑되는지 확인하기 위해, 테스트 코드를 작성해 보았다. 예상되는 Exception을 설정하였고, 결과는 예상대로 JsonProcessingException이 발생했고 Unrecognized field "memberName" 내용을 확인할 수 있었다.

     

    또한, getMemberName() 메서드가 작성되었다면, JSON 데이터로 내보낼 때 {"memberName" : "value"}의 형태로 내보내지게 된다.

     

    결론적으로, 위와 같은 방식은 API 규격을 깨트릴 수 있기 때문에, 필드와 getter() 메서드로만 구성된 클래스에서는 필드명과 getter() 메서드명을 반드시 일치시켜 주어야 한다.

     

    4. getter() 메서드에서 임의의 내용을 반환하는 경우
    public class Member {
        private String name;
        
        public String getName() {
            return "Member Name";
        }
    }

    위 예제와 같이 getter() 메서드에서 임의로 지정한 값을 반환할 때는 어떻게 되는지 알아보겠다.

     

    @Test
    @DisplayName("getter() 메서드에서 임의의 내용을 반환하는 경우")
    public void test02() throws JsonProcessingException, JSONExeption {
        Member member = mapper.readValue(json.toString, Member.class);   // JSON 데이터 -> POJO 객체
        
        logger.info(member.toString());
        
        assertEquals(id, member.getId());
        assertNotEquals(name, member.getName());   // 서로 다른 값
        assertEquals(address, member.getAddress());
        assertEquals(email, member.getEmail());
        
        String resultJson = mapper.writeValueAsString(member);   // POJO 객체 -> JSON 데이터
        logger.info(resultJson);
        
        assertTrue(resultJson.contains("\\"name\\":\\"Member Name\\""));
    }

    ObjectMapper를 통해 JSON 데이터를 POJO 객체로 바인딩하고, 해당 객체의 로그를 찍어보면 JSON 데이터의 입력값대로 필드에 정상적으로 주입된 것을 확인할 수 있다. 하지만, 해당 객체를 직렬화를 통해 다시 JSON 데이터로 만들면 getter() 메서드에 임의로 지정한 값인 "Member Name"이 보내진다는 것을 확인할 수 있다.

    여기서 알 수 있는 중요한 내용은 getter() 메서드는 단순히 매핑관계를 설정해 주는 역할을 넘어서 직렬화를 통해 JSON 형태로 변환할 때 getter() 메서드가 반환하는 값을 내보낸다는 것이다.

     

    5. private 필드와 setter() 메서드로 이루어진 경우

    Jackson 라이브러리는 getter() 메서드뿐만 아니라 setter() 메서드로도 필드 바인딩이 가능하다.

     

    public class Member {
        private String name;
        
        public void setName(String name) {
            this.name = name;
        }
    }

    위와 같이 private 필드와 setter() 메서드만 존재하는 경우에는 어떻게 되는지 알아보겠다.

     

    @Test
    @DisplayName("private 필드와 setter() 메서드로 이루어진 경우")
    public void test03() throws JsonProcessingException, JSONException {
        Member member = mapper.readValue(json.toString(), Member.class);   // JSON 데이터 -> POJO 객체
        
        String memberInfo = member.toString()
        logger.info(memberInfo);
        
        assertTrue(memberInfo.contains("id=" + id));
        assertTrue(memberInfo.contains("name='" + name + "'"));
        assertTrue(memberInfo.contains("address='" + address + "'"));
        assertTrue(memberInfo.contains("email='" + email + "'"));
    
        String resultJson = mapper.writeValueAsString(member);   // POJO 객체 -> JSON 데이터
        logger.info(resultJson);
    }

    테스트를 진행하면 아래와 같은 에러가 발생하면서 테스트가 실패한다.

     

     

    setter() 메서드만 있고, getter() 메서드가 없기 때문에 직렬화를 하는 데 있어서 참고할 프로퍼리가 존재하지 않는다는 내용이다.

    앞서 언급했듯이, private 필드인 경우 getter() 메서드를 기준으로 직렬화를 진행하는데, getter() 메서드가 없으면 직렬화를 진행할 때 참고할 내용이 없어서 내보낼 데이터가 없어서 발생하는 에러이다.

     

    여기서 또 하나 중요한 내용은 JSON 데이터를 POJO 객체로 역직렬화할 때는 setter() 메서드만 있어도 바인딩이 가능해지지만, POJO 객체에서 JSON 데이터로 직렬화할 때는 getter() 메서드가 필수적이라는 것이다.

     

    덧붙여 말하자면, JSON 데이터를 POJO 객체로 역직렬화할 때는 setter() 메서드와 기본 생성자가 필수로 필요하다. 여기서 기본 생성자는 DTO 클래스가 자동으로 생성해 주기 때문에, 생략이 가능한 것이다.

     

    6. setter() 메서드에 임의의 값을 필드에 주입하는 경우

    이번에는 setter() 메서드에 임의로 지정한 값을 필드에 할당하는 경우에 대해서 알아보겠다.

     

    public class Member {
        private String name;
        
        public void setName(String name) {
            this.name = "Member Name;
        }
    }

    위 예제와 같이 setName() 메서드를 통해 임의의 데이터를 필드에 주입하는 경우에 대해서 테스트를 해보겠다.

     

    @Test
    @DisplayName("setter() 메서드에 임의의 값을 필드에 주입하는 경우")
    public void test04() throws JsonParocessingException {
        Member member = mappper.readValue(json.toString, Member.class);   // JSON 데이터 -> POJO 객체
        
        logger.info(member.toString);
        
        assertEquals(id, member.getId());
        assertEquals("Member Name", member.getName());   // 임의로 지정된 값이 Member 객체에 할당
        assertEquals(email. member.getEmail());
    }

    mapper는 기본 생성자를 통해 해당 객체를 생성한 후 setter() 메서드로 JSON으로부터 받아온 데이터를 주입시킨다. 이때 임의로 지정한 값을 setter() 메서드에서 할당받았기 때문에, 당연하게 역직렬화된 Member 객체 안에는 임의로 지정한 값이 할당된 것을 확인할 수 있다.

     

    7. setter() 메서드의 이름이 필드명과 일치하지 않는 경우

    이번에는 setter() 메서드명과 필드명이 일치하지 않는 경우에 대해서 알아보겠다.

     

    public class Member {
        private String name;
        
        public void setMemberName(String name) {
            this.name = name
        }
    }

    위 예제와 같이 setter() 메서드명을 setMemberName()으로 지정하였고, 해당 메서드는 name 필드에 값을 할당하는 역할을 한다.

     

    @Test
    @DisplayName("setter() 메서드의 이름이 필드명과 일치하지 않는 경우")
    public void test05() throws JsonProcessingException, JSONException {
        json.put(MEMBER_NAME, "홍길동");   // {"memberName" : "홍길동"}
        
        Member member = mapper.readValue(json.toString(), Member.class);   // JSON 데이터 -> POJO 객체
        
        logger.info(member.toString);
        
        assertEquals(id, member.getId());
        assertEquals("홍길동", member.getName());   // name 필드에 "홍길동" 값이 할당
        assertEquals(email, member.getEmail());
    }

    위 테스트 코드에서는 {"memberName" : "홍길동"} 형태의 JSON 데이터를 Member 객체로 바인딩하고 있는 것을 알 수 있다.

    key 이름이 memberName이기 때문에, 바인딩하는 과정에서 setMemberName() 메서드를 호출하게 되고, name 필드에는 value인 "홍길동"이 정상적으로 할당되는 것을 확인할 수 있다.

    결론적으로, 역직렬화를 하는 과정에서 사용되는 setter() 메서드는 메서드명에 따라 JSON 데이터를 POJO 객체로 매핑시켜 준다. 따라서, API 규격을 떨어뜨릴 수 있기 때문에, setter() 메서드명과 필드명을 일치시켜 주는 것이 바람직하다.


    출처

    https://beaniejoy.tistory.com/75

     

    728x90

    댓글

Designed by Tistory.