Jackson의 모든 것 - 직렬화 편
모든 소스는 여기서 확인하실 수 있습니다.
Jackson Serialization
jackson이 제공하는 직렬화(serialization) 애노테이션에 대해 알아봅시다.
@JsonAnyGetter
Map 필드를 표준 property로 사용할 수 있는 유연성을 제공합니다.
package io.lcalmsky.jackson_test.domain;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import java.util.HashMap;
import java.util.Map;
public class ExtendableBean {
public String name;
private Map<String, Object> properties;
public ExtendableBean(String name) {
this.name = name;
this.properties = new HashMap<>();
}
@JsonAnyGetter
public Map<String, Object> getProperties() {
return properties;
}
public void add(String key, Object value) {
properties.put(key, value);
}
}
package io.lcalmsky.jackson.domain;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class ExtendableBeanTests {
@Test
@DisplayName("JsonAnyGetter를 사용하여 JSON 작성하기")
public void givenSerializingUsingJsonAnyGetter_whenWriteJson_thenCorrect() throws JsonProcessingException {
// given
ExtendableBean bean = new ExtendableBean("hydralisk");
bean.add("mineral", 100);
bean.add("gas", 50);
bean.add("morphable", true);
bean.add("damage", 12);
bean.add("armor", 0);
// when
String result = new ObjectMapper().writeValueAsString(bean);
// then
assertAll(
() -> assertThat(result, containsString("mineral")),
() -> assertThat(result, containsString("gas")),
() -> assertThat(result, containsString("morphable")),
() -> assertThat(result, containsString("damage")),
() -> assertThat(result, containsString("armor"))
);
// log
System.out.println(result);
}
}
@JsonGetter
@JsonProperty를 대신하여 getter 메소드에 사용하는 애노테이션 입니다.
바로 위에서 사용했던 ExtendableBean 클래스에 아래와 같은 메소드를 추가한 뒤 동일한 테스트를 실행시켜보면
@JsonGetter
public String getUnitName() {
return this.name;
}
{"unitName":"hydralisk","morphable":true,"damage":12,"armor":0,"gas":50,"mineral":100}
키 값이 바뀐 것을 확인하실 수 있습니다.
@JsonPropertyOrder
직렬화 할 때 property의 순서를 지정할 수 있는 애노테이션 입니다.
package io.lcalmsky.jackson_test.domain;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@JsonPropertyOrder({"name", "age", "id"})
public class OrderedBean {
private String id;
private String name;
private int age;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package io.lcalmsky.jackson.domain;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.UUID;
public class OrderedBeanTests {
@Test
@DisplayName("@JsonPropertyOrder 적용한 뒤 순서 확인")
public void givenOrderedBeanUsingJsonPropertyOrder_whenWriteJson_thenOrderedJson() throws JsonProcessingException {
// given
OrderedBean orderedBean = new OrderedBean();
orderedBean.setId(UUID.randomUUID().toString());
orderedBean.setName("홍길동");
orderedBean.setAge(30);
// when
String json = new ObjectMapper().writeValueAsString(orderedBean);
// then
System.out.println(json);
}
}
{"name":"홍길동","age":30,"id":"3e824dc5-10d8-4bfe-822f-1b3432f20231"}
애노테이션에 명시한대로 name, age, id 순서로 JSON이 생성되는 것을 확인할 수 있습니다.
@JsonRawValue
Jackson이 속성을 그대로 직렬화하도록 하는 애노테이션 입니다.
package io.lcalmsky.jackson_test.domain;
import com.fasterxml.jackson.annotation.JsonRawValue;
public class RawBean {
private String name;
@JsonRawValue
private String json;
public RawBean(String name, String json) {
this.name = name;
this.json = json;
}
public String getName() {
return name;
}
public String getJson() {
return json;
}
}
package io.lcalmsky.jackson.domain;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class RawBeanTests {
@Test
@DisplayName("JSON값 그대로 직렬화한뒤 확인")
public void givenJsonRawValueApplied_whenWriteJson_thenRawValue() throws JsonProcessingException {
// given
RawBean bean = new RawBean("My bean", "{\"attr\":false}");
// when
String result = new ObjectMapper().writeValueAsString(bean);
// then
assertTrue(result.contains("My bean"));
assertTrue(result.contains("{\"attr\":false}"));
// log
System.out.println(result);
}
}
{"name":"My bean","json":{"attr":false}}
@JsonValue
인스턴스 직렬화를 위해 사용하는 메소드를 지정합니다.
package io.lcalmsky.jackson_test.domain.support;
import com.fasterxml.jackson.annotation.JsonValue;
public enum EnumWithValue {
ENUM1(1, "TYPE1"),
ENUM2(2, "TYPE2");
private Integer intValue;
private String strValue;
private EnumWithValue(Integer intValue, String strValue) {
this.intValue = intValue;
this.strValue = strValue;
}
@JsonValue
public Integer getIntValue() {
return intValue;
}
public String getStrValue() {
return strValue;
}
}
package io.lcalmsky.jackson.domain.support;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class EnumWithValueTests {
@Test
@DisplayName("@JsonValue를 사용하여 enum 타입을 직렬화하기")
public void givenJsonValueApplied_whenSerialize_thenCorrect() throws JsonProcessingException {
// given
EnumWithValue enum1 = EnumWithValue.ENUM1;
// when
String enumValue = new ObjectMapper().writeValueAsString(enum1);
// then
assertEquals("1", enumValue);
// log
System.out.println(enumValue);
}
}
@JsonValue를 getIntValue()에 지정하였으므로 EnumWithValue 타입의 객체 ENUM1를 역직렬화하였을 때 intValue인 "1"로 변환(writeValueAsString 메소드를 사용하였으므로 정수로 사용하려면 형변환 필요)됩니다. @JsonValue를 getStrValue()에 지정하였다면 "TYPE1"로 변환되었겠죠!
@JsonSerialize
객체로 직렬화 할 때 커스텀 시리얼라이저를 사용하겠다는 애노테이션 입니다.
package io.lcalmsky.jackson_test.domain;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.lcalmsky.jackson_test.domain.serializer.CustomDateSerializer;
import java.time.LocalDate;
public class BeanWithSerializer {
private String name;
@JsonSerialize(using = CustomDateSerializer.class)
private LocalDate time;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDate getTime() {
return time;
}
public void setTime(LocalDate time) {
this.time = time;
}
}
package io.lcalmsky.jackson_test.domain.serializer;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.time.LocalDate;
public class CustomDateSerializer extends JsonSerializer<LocalDate> {
@Override
public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(String.format("%s년 %s월 %s일", value.getYear(), value.getMonthValue(), value.getDayOfMonth()));
}
}
package io.lcalmsky.jackson.domain;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class BeanWithSerializerTests {
@Test
@DisplayName("CustomSerializer를 사용해 역직렬화하기")
public void givenUsingCustomSerializer_whenWriteJson_thenCorrect() throws JsonProcessingException {
// given
BeanWithSerializer beanWithSerializer = new BeanWithSerializer();
beanWithSerializer.setName("날짜");
beanWithSerializer.setTime(LocalDate.of(2019, 11, 15));
// when
String json = new ObjectMapper().writeValueAsString(beanWithSerializer);
// then
assertTrue(json.contains("2019년 11월 15일"));
// log
System.out.println(json);
}
}
LocalDate 값을 yyyy년 MM월 DD일 형식으로 출력하는 것을 확인하실 수 있습니다. 꼭 커스텀 시리얼라이저를 지정하지 않아도 기본으로 제공하는 시리얼라이저를 사용하실 수도 있습니다.
다음 포스팅은 역직렬화(Deserialization) 애노테이션에 대해 확인해보겠습니다.