Json을 파싱할 수 있는 ObjectMapper 예시를 소개한다.
많이 알려져 있지만, 자주 실수할 수 있는 내용과 좋은 팁을 소개한다.
먼저, UnrecognizedPropertyException을 많이 발생시킬 수 있는 내용을 소개한다.
아래는 무난하게 잘 동작하는 코드이다.
import java.util.List;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonTest {
@Test
public void mapperObject() throws Exception {
ObjectMapper mapper = new ObjectMapper();
String jsonInString = "{\"event_code\":112233,\"messages\":[\"hello\",\"java\"],\"name\":\"x112x1121\"}";
EventData eventData = mapper.readValue(jsonInString, EventData.class);
System.out.println(eventData);
}
}
class EventData {
@JsonProperty("event_code")
private int eventCode;
@JsonProperty("name")
private String name;
@JsonProperty("messages")
private List<String> messages;
}
이 코드가 정상적으로 잘 동작하려면, json이 EventData에 맞게 오지 않거나 최소한 해당 json 노드이외에는 전달되지 않는다는 보장이 있어야 한다.
갑자기 json 문자열에 새로운 노드가 새로 추가되면, com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException이 발생한다.
String jsonInString = "{\"event_code\":112233,\"messages\":[\"hello\",\"java\"],\"name\":\"x112x1121\", \"emp\":\"samuel\"}";
예외 내용은 다음과 같다.
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "emp" (class com.kakao.story.util.EventData), not marked as ignorable (3 known properties: "event_code", "name", "messages"])
at [Source: {"event_code":112233,"messages":["hello","java"],"name":"x112x1121", "emp":"samuel"}; line: 1, column: 77] (through reference chain: com.kakao.story.util.EventData["emp"])
at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:51)
at
예외를 발생시키지 않고 안전하게 해결하려면 @JsonIgnoreProperties(ignoreUnknown = true)을 클래스 정의에 둔다. 다른 방법도 있지만, 이 방식이 아주 많이 사용되는 방식이다.
import java.util.List;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonTest {
@Test
public void mapperObject() throws Exception {
ObjectMapper mapper = new ObjectMapper();
String jsonInString = "{\"event_code\":112233,\"messages\":[\"hello\",\"java\"],\"name\":\"x112x1121\", \"emp\":\"samuel\"}";
EventData eventData = mapper.readValue(jsonInString, EventData.class);
System.out.println(eventData);
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
class EventData {
@JsonProperty("event_code")
private int eventCode;
@JsonProperty("name")
private String name;
@JsonProperty("messages")
private List<String> messages;
}
참고로, 코드는 문제가 없어보이지만, org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "event_code" (Class com.kakao.story.util.EventData), not marked as ignorable 예외가 발생할 수 있다.
ObjectMapper 클래스의 fully qualified name을 유심히 살펴본다.
import org.codehaus.jackson.map.ObjectMapper;
import java.util.List;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.DeserializationConfig;
public class JsonTest {
@Test
public void mapperObject() throws Exception {
ObjectMapper mapper = new ObjectMapper();
String jsonInString = "{\"event_code\":112233,\"messages\":[\"hello\",\"java\"],\"name\":\"x112x1121\", \"emp\":\"samuel\"}";
EventData eventData = mapper.readValue(jsonInString, EventData.class);
System.out.println(eventData);
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
class EventData {
@JsonProperty("event_code")
private int eventCode;
@JsonProperty("name")
private String name;
@JsonProperty("messages")
private List<String> messages;
}
import com.fasterxml.jackson.databind.ObjectMapper를 사용해야지 org.codehaus.jackson.map.ObjectMapper를 사용하면 원인모를 미궁에 빠질 수 있다.
또 다른 팁은 serialization시 null에 대한 핸들링이다. 아래 코드를 살펴본다.
import java.util.List;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonTest {
@Test
public void mapperObject() throws Exception {
ObjectMapper mapper = new ObjectMapper();
EventData eventData = new EventData();
eventData.eventCode = 123123;
eventData.name = "wang.lee";
String jsonString = mapper.writeValueAsString(eventData);
System.out.println(jsonString);
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
class EventData {
@JsonProperty("event_code")
int eventCode;
@JsonProperty("name")
String name;
@JsonProperty("messages")
List<String> messages;
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
실행하면, 저장 안한 키의 값을 null로 serialization한다.
{"event_code":123123,"name":"wang.lee","messages":null}
최적화된 json 정보를 유지하기 위해 null 정보를 뺄 수 있다.
Pojo 클래스에 @JsonInclude(Include.NON_NULL)을 추가하면, null 정보를 뺄 수 있다.
import java.util.List;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.JsonInclude;
import static com.fasterxml.jackson.annotation.JsonInclude.Include;
public class JsonTest {
@Test
public void mapperObject() throws Exception {
ObjectMapper mapper = new ObjectMapper();
EventData eventData = new EventData();
eventData.eventCode = 123123;
eventData.name = "wang.lee";
String jsonString = mapper.writeValueAsString(eventData);
System.out.println(jsonString);
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
//@JsonInclude(Include.NON_NULL)
@JsonInclude(value=Include.NON_ABSENT, content=Include.NON_EMPTY)
class EventData {
@JsonProperty("event_code")
int eventCode;
@JsonProperty("name")
String name;
@JsonProperty("messages")
List<String> messages;
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
결과는 다음과 같다.
{"event_code":123123,"name":"wang.lee"}
Java8과 guava에서도 쓸 수 있게 @JsonInclude(value=Include.NON_ABSENT, content=Include.NON_EMPTY)를 사용했다. java7이하에서는 @JsonInclude(Include.NON_NULL)를 사용할 수 있을 것이다.
참고로 @JsonInclude에 대해 설명하면 다음과 같다.
Include.NON_ABSENT는 값이 null이나 java8, guava의 optional의 내부값이 null일 때 serialization하지 않는다.
Include.ALWAYS는 값과 상관없이 serialization한다.
Include.NON_DEFAULT는 값이 기본 설정과 다른 경우 속성이 serialization한다.
Include.NON_EMPTY는 값이 null이 아니거나 empty하지 않으면 serialization한다.
Include.NON_NULL는 null이 아닐 때만 serialzation한다.