알고보면 만만한 Jackson Custom Serialization
API 서버를 만들다보면 어떤 객체를 JSON으로 만들때, 특정 필드만 제외하거나 특정 필드의 이름을 바꿔야 하는 일이 생길 수 있다.
그 객체를 JSON으로 만들 때 특정 필드의 이름을 항상 바꾸려면 해당 필드에 @JsonProperty("새이름")
을 명시하면 되고, 특정 필드를 항상 제외한다면 그냥 객체 클래스에 @JsonIgnoreProperties({"제외할필드명1", "제외할필드명2"})
을 명시하면 된다. 하지만, 항상이 아니라 상황에 따라 다른 방식으로 Serialize 해야한다면 이 방법이 통하지 않는다.
Java 기반 API 서버라면 JSON 처리를 위해 Jackson을 많이 사용하는데, Jackson으로 Java 객체를 JSON으로 Serialize 할 때 특정 필드 제외/이름바꾸기 등 커스터마이징 하는 방법을 검색해보면, 앞에서 말한 @JsonProperty
나 @JsonIgnoreProperties
를 포함해서 꽤나 다양하게 많이 나온다.
- 뭔 필터를 만들고, 프로바이더를 꽂고 어쩌고 자시고 지지고 볶고 태우는 방법도 있고,
@JsonView
라고 하는, 일종의 profile과 비슷한 기능을 하는 애노테이션을 사용하는 방법도 있고,- Jackson을 커스터마이징 할 생각은 아예 포기하고, 그 대신 JSON 화 할 대상 객체 A를 커스터마이징해서 별도의 객체 B를 다시 만들고, B를 대상으로 그냥
objectMapper.writeValueAsString(B)
로 시원하게 처리하는 방법도 있고..(이건 좀.. ㅋㅋ), - 기타 등등 다양하다.
Jackson을 기준으로, 개인적으로 생각할 때 가장 직관적이어서 이해하기 쉽고, 유지보수 하기도 쉽고, 코드량도 적은 커스터마이징 방법 중의 하나인 Jackson Custom Serializer를 만드는 방법을 알아보자.
얼개
Jackson을 이용한 JSON Serialization은 결국 objectMapper.writeValueAsString(객체)
고, 따라서 objectMapper
에 Custom Serializer를 어떤 식으로 집어 넣느냐 하는 문제인데, 이와 관련된 구조만을 추려서 요약하면 다음 그림과 같다.
objectMapper
는 여러 개의 module
을 가질 수 있고, module
도 여러 개의 customSerializer
를 가질 수 있다.
절차
이 얼개를 바탕으로 serialization을 커스터마이징하려면 다음의 절차가 필요하다.
- 실제 커스터마이징을 담당하는 customSerializer를 만든다
- customSerializer를 module에 추가한다.
- module을 objectMapper에 등록한다.
결국 customSerializer
만 잘 만들어주면 된다. 참 쉽다 ㅋㅋ.
CustomSerializer 만들기
JSON serialization 과정에서 커스터마이징이라고 할 수 있는 것은, 결국 앞에서 말한대로 특정 필드의 의도적인 제외, 특정 필드 이름의 변경 정도다. 그래서 만들어야 할 코드는 대략 아래 정도일 것 같다.
1 | 필드1이름지정(field1Key); |
CustomSerializer를 만들려면 JsonSerializer
클래스를 상속해야 한다. 그리고 IDE를 통해 Override 해야할 메서드를 자동 생성하면 대략 아래와 같은 코드가 자동 생성 된다.
1 | package homo.efficio.json.selective.serialization.jackson; |
이제 앞에서 예상했던 코드를 JsonSerializer가 정해준 방식에 맞게 구현해주면 되는데, 그 방식이라는게 아주 간단하다.
다만, 아래 코드의 주석에 표시한 대로 gen.writeObject()
부분이 중첩되어 있는 객체를 재귀 방식으로 풀어서 마법 같은 묘수를 부린다는 점만 기억해두면 좋겠다.
1 |
|
글로 바꿔써보면 그냥 JSON 문자열을 만드는 것과 똑같다.
gen.writeStartObject()
로{
를 열고,
gen.writeFieldName(fieldName)
로 필드 이름을 쓰고,
- serialize 할 값이 primitive라면
gen.writeString(primitive)
로 값을 쓰고,- serialize 할 값이 reference라면
gen.writeObject(reference)
로 값을 쓰고,gen.writeEndObject()
로}
를 닫는다.
""
,:
,,
,[]
등은 고맙게도 Jackson이 알아서 처리해준다능..
이렇게 보니 엄청 직관적이고 당연해 보이기도 한다. 근데 필터나 프로바이더 방식은 이렇게 당연해보이지 않고 복잡하더라능..
이제 방금 만든 customSerializer
를 module
에 추가해보자.
module 만들고 customSerializer 추가
customSerializer
는 만들었는데, module
은 어떻게 만들어야 하지?
간단하다. Jackson은 SimpleModule
이라는 클래스를 제공해주며, 아래의 코드가 전부다.
1 | SimpleModule simpleModule = new SimpleModule(); |
module
에 customSerializer
를 추가하는 코드도 간단하다. 하지만 주석에 표시한 의미는 이해하고 넘어가자.
1 | // FamilyMember 클래스는 FamilyMemberSerializer로 Serialize 하겠다는 의지의 표현. |
objectMapper 만들고 module 추가
objectMapper
는 익숙하니까 설명할 것도 없다. 그냥 코드를 보자.
1 | ObjectMapper objectMapper = new ObjectMapper(); |
실제 사용
1 | objectMapper.writeValueAsString(serialize_할_객체); |
실전 배치
더는 설명할 것도 없다. 잔소리 따위는 접고 그냥 코드를 보면 바로 이해된다. ㅋㅋ
https://gist.github.com/HomoEfficio/e3cee0071f0ce84ed6d7791d0410d8d5
정리
Jackson으로 JSON Serialize 할 때, 필드 이름 변경, 특정 필드 제외 등 커스터마이징이 필요하면,
JsonSerializer
를 상속받아customSerializer
를 만들고,
gen.writeStartObject()
,gen.writeFieldName()
,gen.writeString()/gen.writeObject()
,gen.writeEndObject()
로 serialize 로직을 원하는 대로 구성해서,SimpleModule
에customSerializer
를 추가하고,objectMapper
에simpleModule
를 등록하고,objectMapper.writeValueAsString(serialize_할_객체)
로 serialize 하면 된다.