[Jackson] readValue()로 객체를 파싱할 때 주의하자
Jackson 라이브러리로 json 데이터를 객체로 쉽게 변환할 수 있다.
변환하는 과정에서 종종 의도대로 변환이 안됐던 적이 있어서 케이스 별로 나눠서 테스트 코드를 만들어봤다.
객체로 변환하는 메서드는 readValue()
를 사용할 것이다.
의존성은 아래와 같이 설정했다.
implementation("com.fasterxml.jackson.core:jackson-core:2.15.2")
implementation("com.fasterxml.jackson.core:jackson-annotations:2.15.2")
implementation("com.fasterxml.jackson.core:jackson-databind:2.15.2")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2")
name, age, email 이라는 필드를 갖는 간단한 클래스 Person
을 정의해주자.
age
, email
에는 기본값이 있지만, name
에는 기본값이 없는 점을 유의하자.
data class Person(
val name: String, // 기본값 없음
val age: Int = 0,
val email: String = "test@email.com",
)
케이스 별로 나눠보기
필드 값이 모두 있을 때
name, age, email에 모두 값을 정상적으로 채워주면 파싱도 정상적으로 되는걸 확인할 수 있다.
@Test
fun `json 값을 객체로 파싱할 수 있다`() {
val input = """
{
"name": "John",
"age": 20,
"email": ""
}
""".trimIndent()
jacksonObjectMapper().readValue(input, Person::class.java).also {
assertEquals("John", it.name)
assertEquals(20, it.age)
assertEquals("", it.email)
} // true
}
매칭되지 않는 필드 값이 있을 때
name, age, email 외에 Person에 없던 필드가 json에 있으면 어떻게 될까? unknown
이라는 필드를 추가해보자.
과연 파싱이 잘 될까?
없던 필드는 엄격하게 체크를 하는지, UnrecognizedPropertyException를 던진다.
@Test
fun `json 값에서 파싱할 때 매칭되지 않는 필드는 설정을 해주지 않으면 무시할 수 없다`() {
val input = """
{
"name": "John",
"age": 20,
"email": "",
"unknown": "field" // 추가
}
""".trimIndent()
// 없던 필드가 생겨서 UnrecognizedPropertyException 를 던진다.
assertThrows(UnrecognizedPropertyException::class.java) {
jacksonObjectMapper().readValue(input, Person::class.java)
}
}
result type(Person
)에 안맞으면 DatabindException
을 던진다고 나와있다.
- 참고로 예외 연관관계는 아래와 같다.
- UnrecognizedPropertyException → PropertyBindingException → MismatchedInputException → JsonMappingException → DatabindException
아래처럼 FAIL_ON_UNKNOWN_PROPERTIES 설정을 false
로 없는 필드는 무시할 수 있다!
DeserializationFeature.java 에서 FAIL_ON_UNKNOWN_PROPERTIES
의 기본값은 true
다.
그래서 처음에 파싱이 안된 것이다.
@Test
fun `json 값에서 파싱할 때 매칭되지 않는 필드는 설정해주면 무시할 수 있다`() {
val input = """
{
"name": "John",
"age": 20,
"email": "",
"unknown": "field"
}
""".trimIndent()
jacksonObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) // 추가
.readValue(input, Person::class.java)
.also {
assertEquals("John", it.name)
assertEquals(20, it.age)
assertEquals("", it.email)
}
}
기본값이 없는 필드만 있을 때
기본값이 없는 필드는 반드시 값이 있어야 객체 생성이 되기 때문에, 값을 채워줘야 한다.
기본값이 있는 필드는 json 데이터에 없으면 기본값으로 채워진다. (age
는 0, email
은 test@email.com)
@Test
fun `기본값이 없는 필드만 채워주면 json 파싱에 성공한다`() {
val input = """
{
"name": "John"
}
""".trimIndent()
// true
jacksonObjectMapper().readValue(input, Person::class.java).also {
assertEquals("John", it.name)
assertEquals(0, it.age)
assertEquals("test@email.com", it.email)
}
}
jacksonObjectMapper
대신ObjectMapper
를 사용하면InvalidDefinitionException
를 던진다.- 이건 jackson-module-kotlin이 jacksonObjectMapper에 적용돼있어서 그렇다. (stackoverflow 참조)
- 이건 jackson-module-kotlin이 jacksonObjectMapper에 적용돼있어서 그렇다. (stackoverflow 참조)
기본값이 없는 필드(name
)이 없으면 파싱에 실패하면서 JsonProcessingException
를 던지게 된다.
@Test
fun `기본값이 없는 필드를 채우지 않으면 json 파싱에 실패한다`() {
val input = """
{
"age": 20,
"email": ""
}
""".trimIndent()
// 기본값이 없는 필드 name의 값을 못채우니 파싱에 실패한다. (JsonProcessingException)
assertThrows(JsonProcessingException::class.java) {
jacksonObjectMapper().readValue(input, Person::class.java)
}
}
댓글남기기