-
Jackson ObjectMapper의 동작과정프로젝트/게시판 프로젝트 2022. 6. 20. 11:07
이 글은 아래의 정보들을 다루었습니다.
ObjectMapper란?
ObjectMapper를 사용하던 중 발생한 에러 발생과 해결방법
ObjectMapper의 동작과정(리플랙션과 기본생성자, Getter/Setter)
Object Mapper란?
Object Mapper는 데이터의 형식을 변환해줍니다.
아래 예시와 같이 데이터의 형식을 변환할 수 있습니다.
Text(Json) -> Object
Object -> Text(Json)
보통 Spring에서 api를 개발하다 보면 @ResetController를 많이 사용합니다.
이때 request와 response에서 json -> 객체, 객체 -> json의 과정은 MessageConverter에 의해 처리됩니다.
MessageConverter가 json 변환 처리 과정에서 사용하는 것이 바로 Jackson입니다.
개요
프로젝트를 진행하던 중 ObjectMapper로 DTO 클래스를 Json으로 파싱 하는 작업을 수행하였습니다.
코드
private MemberRequestDTO getMemberRequestDTO() { Address myAddress = Address.builder() .address1("경기도 시흥시") .address2("XX아파트 XX호") .zipCode("429-010") .build(); MemberRequestDTO memberRequestDTO = MemberRequestDTO.builder() .userId("junwooKim") .name("KIM") .nickName("junuuu") .password("123456789") .phoneNumber("01012345678") .address(myAddress) .build(); return memberRequestDTO; }
위의 코드는 회원가입시 입력값으로 들어올 수 있는 MemberRequestDTO를 생성해주는 코드입니다.
특이점으로는 내부에 Address라는 DTO 객체를 가지고 있습니다.
MemberRequestDTO memberRequestDTO = getMemberRequestDTO(); String body = (new ObjectMapper()).writeValueAsString(memberRequestDTO);
코드를 간단하게 소개하자면 위의 코드에서 회원정보 입력값을 가진 memberRequestDTO를 받아와서 ObjectMapper의 writeValueAsString 메서드를 통해 Json 형태의 String으로 반환합니다.
에러 발생
컴파일 시 발생한 문제는 아니지만 Json 형태로 제대로 변환되지 않는 것을 확인했습니다.
따라서 문제가 발생할 수 있는 부분을 탐색하기 시작했고 결과적으로 ObjectMapper 쪽에 문제가 있다는 것을 확인했습니다.
문제의 ObjectMapper 위/아래로 print를 찍어보았습니다.
다음은 문제의 ObjectMapper 앞뒤로 print 찍은 결과입니다.
getAddress를 수행하였을 때 Address는 제대로 출력이 되는 모습을 확인할 수 있습니다.
하지만 Json 형태로 변환될때는 address에 {} 아무것도 들어가지 않는 모습을 확인할 수 있습니다.
문제 해결하기
직렬화과정에서 문제가 되는 것이 아닐까?라는 의심에 Address 클래스를 Serializable을 implements 해보았지만 문제는 해결되지 않았습니다.
memberRequestDTO도 DTO이고 Address도 DTO인데 왜 하나는 잘 수행되고 하나는 잘 수행되지 않는지에 초점을 맞추기로 했습니다.
코드레벨로 분석하던 도중에 memberRequestDTO에는 @Getter가 있었지만 Address에는 @Getter가 존재하지 않았습니다.
그러던 문득 ObjectMapper를 사용할 때는 @Getter가 존재했어야 했었던 기억이 어렴풋이 떠오르면서 해결할 수 있었습니다.
따라서 다음에 또 이런일이 일어나지 않기 위해 조금 더 ObjectMapper의 동작 과정에 대해서 이해해보고 사용하고자 합니다.
ObjectMapper의 동작과정
Jackson은 기본적으로 프로퍼티로 동작합니다.
프로퍼티란 getter와 setter로 이해하시면 됩니다.
Jackson은 JSON 필드의 이름을 Java 오브젝트의 getter 및 setter 메서드와 일치시켜 JSON 오브젝트의 필드를 Java 오브젝트의 필드에 매칭 합니다.
메서드 이름의 "get" / "set" 부분을 제거하고 나머지 이름의 첫 문자를 소문자로 변환합니다.
이런 점 때문에 Jackson 라이브러리를 사용하여 json 문자열로 Serialize 할 때는 getter를 주의해야 합니다.
아래의 코드에는 getProductInfo라는 getXX 메서드가 존재합니다.
@Builder @NoArgsConstructor @AllArgsConstructor @Getter public class Product { private long id; private String name; private long price; public String getProductInfo(){ return name + price; } }
이 상태로 객체를 json으로 변환한다면 결과는 다음과 같습니다.
(객체의 상태 id = 1 , name = KEYBOARD, price=1000)
{"id":1,"name":"KEYBOARD","price":1000,"productInfo":"KEYBOARD1000"}
productInfo라는 필드가 포함되어 있는 사실을 볼 수 있습니다.
심지어 해당 json 문자열을 다시 읽어 들여 자바 객체로 deserialize 할 때는 인식되지 않은 필드라면서 파싱 에러가 발생합니다.
해결방법으로는 @JsonIgnore 어노테이션을 통해 직렬화 대상이 아닌 것으로 제외시켜줍니다.
@Builder @NoArgsConstructor @AllArgsConstructor @Getter public class Product { private long id; private String name; private long price; @JsonIgnore public String getProductInfo(){ return name + price; } }
기본 생성자가 필요한 이유
DTO에 필드 값을 주입하는 과정을 쭉 타고 들어가 보면 최종적으로 reflection을 사용해서 값을 주입합니다.
_filed는 java.lang.reflect 패키지의 Field 자료형입니다.
필드 값을 주입하는 과정을 좀 더 깊게 알아보고 싶다고 하시는 분은 다음 링크에서 확인해주세요!
즉, reflection을 통해 값을 주입하기 때문에 기본 생성자는 필요하고 Setter는 필요하지 않습니다.
또한 이때 접근제어자를 private으로 설정해도 리플렉션은 접근 제어자와 상관없기 때문에 특이사항이 없다면 private으로 설정하여 빈 객체 생성을 막아주는 것이 좋을 것 같습니다.
다양한 상황
1. Getter / Setter 없음 : 에러 발생 o
2. Getter만 있음 : 에러 발생 x
3. Setter만 있음 : 에러 발생 x
심화과정
ObjectMapper를 위한 기본 생성자를 사용할 것인지에 대한 이슈입니다.
ConstructorProperties와 JsonProperties에 대해서 알아보고자 합니다.
다음은 기본 생성자 없이 Jackson이 객체를 파싱 하는 방법입니다.
// User.java @Getter public class User { @JsonProperty("user_name") private final String userName; private final int age; @JsonCreator // 생성자가 하나면 생략 가능 public User(@JsonProperty("user_name") String userName, @JsonProperty("age") int age) { this.userName = userName; this.age = age; } }
파싱에 필요한 필드를 갖는 생성자에 @JsonCreator를 붙여 Jackson에게 이를 사용하라고 알려줍니다.
만약 클래스에 생성자가 하나뿐이라면 이를 생략해도 됩니다.
다음은, 생성자 내에 파라미터 모두 @JsonProperty를 붙여 Jackson에게 접근할 필드명을 알려줍니다.
이때 @ConstructorProperties를 사용하면 생성자 내 파라미터에 모두 @JsonProperty를 붙이지 않아도 됩니다.
// User.java @AllArgsConstructor(onConstructor_ = {@ConstructorProperties({"user_name", "age"})}) @Getter public class User { @JsonProperty("user_name") private final String userName; private final int age; }
출처
https://kim-solshar.tistory.com/85
https://da-nyee.github.io/posts/woowacourse-why-the-default-constructor-is-needed/
https://skyblue300a.tistory.com/13
'프로젝트 > 게시판 프로젝트' 카테고리의 다른 글
인터셉터를 활용한 인증/인가 공통처리 (0) 2022.06.29 JPA 게시판 엔티티 만들기 (0) 2022.06.23 AWS EC2 build 무한로딩 에러(EC2 메모리 부족 현상 대처) (0) 2022.06.16 AWS EC2에 스프링부트 프로젝트 배포하기 (0) 2022.06.14 AWS RDS 구축하기 (2) 2022.06.11