이슈
코틀린을 사용하면서 Redis에서 value에 직렬화/역직렬화 클래스를 설정할 일이 있어 해당 구현체로 Jackson을 사용하기로했다.
그래서 ObjectMapper를 커스텀해서 사용하던 도중 어느 한 dto data class를 직렬화할때 에러가 났다.
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Unwrapped property requires use of type information:
cannot serialize without disabling `SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS`
해당 dto의 프로퍼티는 클래스였고, @JsonUnwrapped을 이용해서 사용자에게 보내줄 땐 json을 펴서 보내주게 되어 있었다.
data class Sample(
@field:JsonUnwrapped
val data1: Data1,
@field:JsonUnwrapped
val data2: Data2,
)
data class Data1(
val name: String,
val age: Int,
)
data class Data2(
val school: String,
val job: String,
)
대략 위와 같은 느낌이다. Sample 클래스를 직렬화할 때 에러가 나는 것이다.
Sample 클래스는 다음과 같이 json으로 해석될 것이다.
{
"name" : ?,
"age" : ?,
"school" : ?,
"job" : ?
}
만약 @JsonUnwrapped이 없다면 다음과 같은 json으로 해석될 것이다.
{
"data1" : {
"name" : ?,
"age" : ?
},
"data2" : {
"school" : ?,
"job" : ?
}
}
찾아보니 해당 SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS 옵션을 False로 두어야 에러가 안난다고 한다. 에러 메세지도 그것을 말하고 있다.
추측하건데 @JsonUnwrapped이 적힌 프로퍼티는 직렬화할 때 타입정보를 저장하지 않나보다. 그래서 에러를 띄우게 되어있는데 이것을 강제로 무시하게 만드는 옵션인 것 같다. 그래서 해당 옵션을 추가해주고 다시 실행해봤다.
fun makeObjectMapper() =
jacksonObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.configure(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS, false)
(이하생략)
그랬더니 이제 잘된다! 싶었지만 처음에만 잘됐고 두 번째 다시 해당 dto를 요청하니까 이번엔 역직렬화에서 문제가 터졌다.
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot define Creator property "data1" as `@JsonUnwrapped`: combination not yet supported
직렬화되어 redis에 저장된 json을 다시 역직렬화할 때 나는 에러 같은데.. 정확히 무슨 말을 하고 싶은지 모르겠다. 찾아도 안나온다.
@JsonCreator를 같이 사용하지 못한다는 에러라는데.. 해당 어노테이션을 사용한 적도 없다 ㅠㅠ
그래서 테스트해봤는데, 직렬화된 json엔 Data1과 Data2의 정보가 없었다. 해당 정보가 없으니 Sample 클래스로 역직렬화가 불가능했던 것이다. 왜냐하면 Sample 클래스의 생성자는 Data1과 Data2를 받게 되어있기 떄무이다.
해결법
가장 쉬운건 먼저 Jackson 사용을 포기하는 것이다. JdkSerializationRedisSerializer 라고 기본 자바 직렬화 구조를 사용하는 클래스가 존재한다. 해당 클래스는 jackson으로 직렬화를 하는게 아니라서 에러가 안난다! 다만 어떤 단점이 있을지는 아직 파악을 못한 상황이다.
두번쨰는 @JsonCreator를 이용해 새로운 생성자를 만들어주는 것이다. 현재 에러나는 이유가 Sample 클래스의 생성자에 Data1 클래스와 Data2 클래스를 받게 되어있는데, 정작 직렬화된 json엔 해당 정보가 없는 것이 이유이니..
data class Sample(
@field:JsonUnwrapped
@field:JsonProperty(access = JsonProperty.Access.READ_ONLY)
val data1: Data1,
@field:JsonUnwrapped
@field:JsonProperty(access = JsonProperty.Access.READ_ONLY)
val data2: Data2,
) {
@JsonCreator
constructor(
@JsonProperty("name")
name: String,
@JsonProperty("age")
age: Int,
@JsonProperty("school")
school: String,
@JsonProperty("job")
job: String,
) : this(
Data1(name, age),
Data2(school, job),
)
}
data class Data1(
val name: String,
val age: Int,
)
data class Data2(
val school: String,
val job: String,
)
이런 식으로 새로 만든 생성자를 이용하게하여 안에서 Data1, Data2 클래스를 만들어 최상위 생성자를 사용하게 하면 잘 된다.
근데 지금은 프로퍼티가 몇 개 없지만 엄청 많아질 경우 굉장히 귀찮고 곤란한 작업이 될 것이다.
나머지는 커스텀 역직렬화 클래스를 각 Dto마다 적용하는 등... 여러 가지 방법이 있지만 너무 복잡하고 현실적이지 않으니 위 두 가지가 가장 현실적인 대안이 될 것같다.
아니면 아예 @JsonUnwrapped 사용을 포기하던가!
'Backend > Spring' 카테고리의 다른 글
spring boot mongo DB LocalDateTime UTC -> KST 한국 시간 전환하기 (0) | 2023.12.16 |
---|---|
CompositeCacheManager의 배치 시간 소요 문제 - CacheManager는 꼭 전부 Bean 등록하자 (0) | 2023.12.02 |