Spring/Jackson

Jackson Annotation 정리 (2) (Deserialization Annotation)

긍.응.성 2020. 9. 11. 22:31
반응형

Jackson에서 사용하는 Annotation에 대해 정리한다. Baeldung (Jackson Annotation Example)에 정리된 글을 참고하였다.

2. Jackson Deserialization Annotations

2.1 @JsonCreator

@JsonCreator는 Json객체를 deserialize 하여 객체 mapping 시 사용할 생성자를 지정할 수 있도록 도와준다. 이때 주의할 점은 @JsonCreator는 반드시 @JsonProperty와 함께 사용되어야 한다. @JsonProperty는 @JsonCreator의 파라미터 필드에 정의되어 Mapping을 위한 property값을 정의한다.

파라미터의 이름이 property 명과 일치한다면 @JsonProperty 애너테이션을 사용하지 않고 처리해도되지 않을까?라는 의문이 생길 수 있다. 하지만 함수가 컴파일되면 파라미터를 'var0', 'var1'과 같이 임의의 이름으로 바뀌게된다. 즉, Jackson이 파싱 한 json데이터를 deserialize 하기 위해 key value값을 얻어와도 이름을 통해서는 어떤 필드에 값을 적용해야 되는지 알 수 없다. 그렇기에 각 파라미터마다 @JsonProperty 애노테이션을 붙여야만 property 이름에 맞는 필드를 찾고 Reflection API로 setting 해줄수 있다.

public class BeanWithCreator {
    public int id;
    public String name;
 
    @JsonCreator
    public BeanWithCreator(@JsonProperty("id") int id, @JsonProperty("theName") String name) {
        this.id = id;
        this.name = name;
    }
}
@Test
public void testJsonCreator() throws IOException {
	String json = "{\"id\":1,\"theName\":\"My bean\"}";

	BeanWithCreator bean = new ObjectMapper().readerFor(BeanWithCreator.class).readValue(json);
	assertEquals(1, bean.getId());
	assertEquals("My bean", bean.getName());
}
Jackson이 deserialize 과정에서 별도의 설정이 없을 때 처리하는 동작은 다음과 같다.
1. 기본 생성자를 이용해서 생성
2. 필드가 public이면 직접 할당
3. 필드가 private이면 setter 사용

위의 과정 중 @JsonCreator는 (기본 생성자) + (setter) 조합을 대체한다. 이 어노테이션이 생성자나 팩토리 메서드 위에 선언되면 Jackson은 해당 함수를 통해 객체를 생성하고 필드를 생성과 동시에 채운다. 이러한 방식은 setter 함수 없이 필드 주입을 시킬 수 있다는 것이 특징이다. Jackson을 통해 immutable 한 객체(setter 함수가 없으므로)를 얻기 위한 하나의 방법으로 사용할 수 있다.

 

2.2 @JacksonInject

@JacksonInject 애노테이션은 선언된 필드가 Json 데이터가 아닌 inject 될 것임을 알려준다.

public class BeanWithInject {
	@JacksonInject
	private int id;

	private String name;

	// getter setter
}
@Test
public void testJacksonInject() throws IOException {
	String json = "{\"name\":\"My bean\"}";

	InjectableValues inject = new InjectableValues.Std()
    	.addValue(int.class, 1); // id값으로 '1' 주입
	BeanWithInject bean = new ObjectMapper().reader(inject)
		.forType(BeanWithInject.class)
		.readValue(json);

	System.out.println(bean.getId());
	System.out.println(bean.getName());
}

/** @JacksonInject 적용 전 */
> 0
> My bean

/** @JacksonInject 적용 후 */
> 1
> My bean

     

2.3 @JsonAnySetter

@JsonAnyGetter는 Map을 properties로 serialize 했다면, @JsonAnySetter는 properties를 Map으로 deserialize 한다. 아래의 테스트는 문자열 json에 대한 deserialization이다. 만약 필드 이름이 "name"과 동일하지 않은 다른 값이었다면 이 값 또한 Map에 key value쌍으로 들어간다.

public class ExtendableBean {
	public String name;
	private Map<String, String> properties = new HashMap<>();

	@JsonAnySetter
	public void add(String key, String value) {
		properties.put(key, value);
	}

	public Map<String, String> getProperties() {
		return properties;
	}
}
@Test
public void testJsonAnySetter() throws IOException {
	String json = "{\"name\":\"My bean\",\"attr2\":\"val2\",\"attr1\":\"val1\"}";

	ExtendableBean bean = new ObjectMapper()
		.readerFor(ExtendableBean.class)
		.readValue(json);
	System.out.println(bean.getProperties());
    // {attr2=val2, attr1=val1}
}

     

2.4 @JsonSetter

method에 대하여 value로 정한 필드의 setter로 지정한다. 아래의 예시는 setTheName() 메서드를 name 필드의 setter로 지정한 모습이다. 즉, deserialization 과정에서 name필드는 setTheName() 메서드를 통해 세팅된다.

public class MyBean {
    public int id;
    private String name;
 
    @JsonSetter("name")
    public void setTheName(String name) {
        this.name = name;
    }
}

 

2.5 @JsonDeserialize

unmarshalling 과정에 custom deserializer를 사용하도록 지정한다. @JsonSerialize와 반대의 역할을 하는 애노테이션이며 이전 게시물에 설명한 serialization과 marshalling의 차이처럼 deserialization과 unmarshalling의 차이를 이해하면 되겠다.

아래의 예시에서는 CustomDataDeserializer를 이용하여 역직렬화하도록 지정해주었다.

public class EventWithSerializer {
	private String name;

	@JsonSerialize(using = CustomDateSerializer.class)
	@JsonDeserialize(using = CustomDateDeserializer.class)
	private Date eventDate;

	public EventWithSerializer() {
	}

	public EventWithSerializer(String name, Date eventDate) {
		this.name = name;
		this.eventDate = eventDate;
	}

	public String getName() {
		return name;
	}

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

	public Date getEventDate() {
		return eventDate;
	}

	public void setEventDate(Date eventDate) {
		this.eventDate = eventDate;
	}
}
public class CustomDateDeserializer extends StdDeserializer<Date> {

	private static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

	public CustomDateDeserializer() {
		this(null);
	}

	public CustomDateDeserializer(Class<?> t) {
		super(t);
	}

	@Override
	public Date deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
		String date = jsonParser.getText();
		try {
			return formatter.parse(date);
		} catch (ParseException e) {
			throw new RuntimeException(e);
		}
	}
}
@Test
public void testJsonDeserialize() throws IOException {
	String json = "{\"name\":\"party\",\"eventDate\":\"2020-09-12 01:12:34\"}";

	SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
	EventWithSerializer event = new ObjectMapper()
		.readerFor(EventWithSerializer.class)
		.readValue(json);

	assertEquals("2020-09-12 01:12:34", df.format(event.getEventDate()));
}

 

2.6 @JsonAlias

@JsonAlias는 필드나 파라미터 위에 선언되어 하나 혹은 그 이상의 대체 property 이름으로 deserialize 되도록 해준다. 아래의 예제는 필드 firstName에 대하여 이름이 fName 혹은 f_name인 property가 나온다면 deserialize 해주도록 지정한다. 두 개 이상의 properties가 모두 등장한다면 (순차적으로 parsing 하는 것 같다) 가장 늦게 선언한 property의 값으로 setting 하는 것을 확인할 수 있다.

public class AliasBean {
	@JsonAlias({ "fName", "f_name" })
	private String firstName;
	private String lastName;

	// getter, setter
}
/**
* 하나의 property만 match (fName)
*/
@Test
public void testJsonAlias_matchOne() throws IOException {
	String json = "{\"fName\": \"John\", \"lastName\": \"Green\"}";

	AliasBean aliasBean = new ObjectMapper()
		.readerFor(AliasBean.class)
		.readValue(json);

	System.out.println(aliasBean.getFirstName()); // John
}

/**
* 두개의 properties match (fName, f_name)
*/
@Test
public void testJsonAlias_matchesTwo() throws IOException {
	String json = "{\"fName\": \"John\", \"f_name\": \"Joe\", \"lastName\": \"Green\"}";

	AliasBean aliasBean = new ObjectMapper()
		.readerFor(AliasBean.class)
		.readValue(json);

	System.out.println(aliasBean.getFirstName()); // Joe
}

 

반응형