직렬화/역직렬화를 할 때 사용해왔고, 필요한 경우에는 검색으로 짧게 이해하고 사용하거나 팀 repository에 적용된 코드를 참고하면서 사용해왔었다.
이번 기회에 찬찬히 읽어보고 정리하는 시간을 가졌다.
Baeldung의 다음 3가지 Article을 읽으며 정리했다.
Introduce to the Jackson ObjectMapper
Spring Boot: Customize the Jackson ObjectMapper
Getting Started with Custom Deserialization in Jackson
Jackson은 Java JSON 라이브러리
로 알려져 있으며, JSON 뿐만 아니라 XML/YAML/CSV 등 다양한 형식의 데이타를 지원하는 data-processing 툴이다.
JSON 관련 라이브러리로 GSON, SimpleJSON도 있지만, 다음과 같은 특징을 가진다.
Advantages of Gson:
Simplicity of toJson/fromJson in the simple cases
For deserialization, do not need access to the Java entities
Advantages of Jackson:
Built into all JAX-RS (Jersey, Apache CXF, RESTEasy, Restlet), and Spring framework
Extensive annotation support
Spring에서 Jackson을 사용하여 이전 포스팅에서 봤던 ObjectMapper를 MessageConverter를 사용하고 있고 다양한 Annotation을 지원하고 있어 주로 사용하게 되었다고 생각한다.
spring-boot-starter-web에는 Jackson를 포함한다.
ObjectMapper objectMapper = new ObjectMapper();
String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
Car car = objectMapper.readValue(json, Car.class);
String carAsString = objectMapper.writeValueAsString(car);
여기서 ObjectMapper에 어떤 설정을 해주고 싶다면 다음과 같이 가능하다.
// Deserialize시 null인 property가 있어도 Exception을 발생시키지 않도록 설정
objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
Custom serializer와 deserializer는 input이나 output JSON 응답으로 직렬화 역직렬화 되어야하는 경우에 유용하다.
다음은 Deserialize 예시로 추상 클래스인 StdDeserializer를 상속받는다.
public class AnimalStdDeserializer extends StdDeserializer<Animal> {
protected AnimalStdDeserializer(Class<?> vc) {
super(vc);
}
@Override
public Animal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode node = p.getCodec().readTree(p);
String type = node.get("type").asText();
String name = node.get("name").asText();
Animal animal = new Animal();
animal.setType(type);
animal.setName(name);
return animal;
}
}
가끔 예시를 보면 JsonDeserializer를 구현하는 경우가 있는데 상속 관계는 다음과 같다.
jackson 에서는 StdDeserializer, StdSerializer를 사용하기를 권장하고 있다.
Custom deserializers should usually not directly extend this class,
but instead extend com.fasterxml.jackson.databind.deser.std.StdDeserializer
두 가지 방식이 있다.
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Animal.class, new AnimalStdDeserializer());
module.addSerializer(Animal.class, new AnimalStdSerializer());
objectMapper.registerModule(module);
String jsonStr = "~~";
Animal animal = mapper.readValue(jsonStr, Animal.class);
public class Cage {
@JsonSerialize(using = AnimalStdSerializer.class)
@JsonDeserialize(using = AnimalStdDeserializer.class)
private Animal animal;
}
ContextualDeserializer Interface를 구현해서 Generic의 Type parameter를 가져올 수 있도록 해야한다.
createContextual method를 overriding하여 역직렬화에 필요한 정보를 추출해 Deserialize 인스턴스를 생성하도록 한다.
public interface ContextualDeserializer
{
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property)
throws JsonMappingException;
}
public class WrapperDeserializer extends JsonDeserializer<Wrapper<?>> implements ContextualDeserializer {
private JavaType type;
@Override // ContextualDeserializer override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
this.type = property.getType().containedType(0);
return this;
}
@Override // JsonDeserializer override
public Wrapper<?> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
Wrapper<?> wrapper = new Wrapper<>();
wrapper.setValue(deserializationContext.readValue(jsonParser, type));
return wrapper;
}
}
이전에 인스턴스 필드 중 개인정보는 masking하여 logging할 때 객체를 serialize하는데 이때도 masking할 필드를 Annoation으로 붙여두고 Annotation이 있는 필드에 대해서 JsonSerializer를 적용하도록 하는 것도 해당 방식을 사용할 수 있다.
Spring Boot에서 요청을 deserialize, 응답을 serialize할 때 ObjectMapper instance를 사용한다.
Spring Boot는 default로 다음 설정을 disable 시켜뒀다.
Spring Boot를 사용할 때 default ObjectMapper의 설정을 customize하거나 override해서 쓸 수 있다.
spring.jackson.<category_name>.<feature_name>=true,false
단점 : 세부적으로 custom하기는 어렵다.
@Configuration
@PropertySource("classpath:coffee.properties")
public class CoffeeRegisterModuleConfig {
@Bean
public Module javaTimeModule() {
JavaTimeModule module = new JavaTimeModule();
module.addSerializer(LOCAL_DATETIME_SERIALIZER);
return module;
}
}
@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
return builder -> builder.serializationInclusion(JsonInclude.Include.NON_NULL)
.serializers(LOCAL_DATETIME_SERIALIZER);
}
또 다른 예시로 @JsonFormat을 응답 객체에 매번 붙여두지 않고 공통적인 형식을 지정하는 용도로 사용할 수 있다.
참고
설정을 완전히 컨트롤하려면 auto configuration을 disable하고 custom 설정을 적용하는 방식이 있다.
ObjectMapper를 생성해서 Primary Bean으로 등록하는 방식으로 configuration을 내가 전부 명시해야한다.
Jackson2ObjectMapperBuilder Bean을 정의하는 방법으로 Spring Boot는 ObjectMapper를 만들 때 이 방식을 사용한다.
1번과 다르게 다음 두개가 default로 설정되어있, 좀 더 간단하고 직관적인 방식이다.
참고
https://github.com/FasterXML/jackson
https://www.baeldung.com/jackson-vs-gson