HTTP 200 OK

Memento mori & Carpe diem

Kotlin

JSON 직렬화가 왜 안돼?..

sjoongh 2024. 11. 24. 13:49

개요

A서버에서 B서버로 body를 전달해주는 테스트를 하던 도중 필드 하나가 미전달 되는 현상이 지속적으로 발생했습니다. 해당 이슈를 해결하기 위해 다방면으로 고민했던 과정과 해결방안을 작성했습니다.

 

해결과정

dto는 아래와 구성했고 해당 객체를 body에 담아 전달해주는 로직을 작성하고 있었습니다. 여느때와 다름없는 작업이었지만 isDelete 필드만 전달 되지 않는 현상이 발생하고 있었습니다. 간단한 문제라고 생각했기에 큰 문제가 아니라고 생각했지만 고난의 시작이었습니다ㅠ..

data class jacksonTest(
    val id : String,
    val name : String,
    val isDelete : String,
    val createdAt : LocalDateTime
)

 

이렇게 봤을때는 아무런 문제도 없다고 생각했고 원인을 찾기 위해 다방면으로 시도해봤습니다.

 

 

1. body에 데이터가 안갈리가 없는데? 분명 담고 있고 request시에는 잘 들어감, 다른 필드들은 들어가는거보니 문제없음 또한 body값을 B서버에 전달했을때 200OK를 받은 상황

-> 연동 문제 아님

1-1. dto에서 값을 입력받을때 값 전달을 못받았나? controller 단에서는 값이 온전히 잘 들어오고 nullable처리도 안했기에 에러가 발생해야함

-> 필드 문제 아님

1-2. B서버로 데이터가 전달되는 과정에서 문제가 있나? isDelete 필드만 제외시키는 설정이???

-> 말도안됨

1-3. 꿈을 꾸고 있나? -> 현실임

 

 

2. JSON 변환자체에 문제가 있는걸까? 라는 생각이 들어서 objectMapper를 사용해서 직접 값을 전달해봤습니다,

val objectMapper = ObjectMapper().apply { 
propertyNamingStrategy = PropertyNamingStrategies.LOWER_CAMEL_CASE 
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) 
}

똑같음.. -> 실패

 

3. 필드명을 인식하지 못하는건가? 싶어 @JsonProperty("isDelete") 로 해당 필드를 명시시켜줬습니다.

-> 실패

 

4. request dto에 존재하는 다른 필드들에서 추가,수정,삭제를 시도해봤지만 isDelete 필드에서만 비슷한 현상이 발생했고ㅠ 다양한 방법으로 값을 전달하려고 시도했지만

-> 전부 실패

 

5. 그러다 isTest라는 필드를 만들어서 테스트 해보던중? 오?? isTest 필드도 전달하지 못하는 상황이 발생했습니다!!

 

is로 시작하는 네이밍에 문제가 있나??... 그동안은 잘 사용하고 있었는데.. 이상한 생각이 들어 GPT에게 조언을 구해봤습니다. 다음과 같은 답변을 받을 수 있었습니다.

기본적으로 Jackson 라이브러리는 is로 시작하는 프로퍼티를 직렬화할 때 해당 필드를 getter로 간주하여 제외하는 경우가 있습니다.

Kotlin의 data class에서 is로 시작하는 Boolean 타입 프로퍼티는 JSON 직렬화 시에 종종 문제를 일으킬 수 있습니다. 기본적으로 Jackson 라이브러리는 is로 시작하는 프로퍼티를 직렬화할 때 해당 필드를 getter로 간주하여 제외하는 경우가 있습니다. 그 결과, JSON 직렬화 시 예상치 못한 필드 이름 변경이 발생하거나 역직렬화 시 필드를 제대로 매핑하지 못하는 문제가 발생할 수 있습니다

 

답변을 받은 후 Jackson의 기본동작 원리가 궁금해 공식문서를 통해 찾아본결과 gitHub에 동일한 이슈가 올라온것을 확인할 수 있었습니다.

 

의문점

Bool타입에도 이슈가 발생할 수 있다는 것을 확인했지만 필자는 Bool타입으로 사용할때는 현재 이슈가 발생했던 경험은 없었으며 'is'로 시작하는 네이밍을 String타입으로 선언해서 사용하는것은 처음이였기에 해당 문제가 발생했다고 생각했고 Bool타입이 아닌 String이 문제가 될 수 있는지 공식문서를 통해 다음과 같은 자료를 찾을 수 있었습니다.

 

Jackson의 기본 동작: Jackson은 Java의 표준 getter 규칙을 따르며, is로 시작하는 메서드를 Boolean 타입의 getter로 취급합니다. 따라서, String타입의 필드 이름이 isSomething일 경우, Jackson은 이를 something으로 간주하여 JSON에 직렬화하거나 역직렬화를 시도합니다. 이로 인해 JSON과 객체 간의 매핑이 정확하지 않을 수 있습니다.

 

 

아하.. 제가 선언했던 isDelete의 타입이 String이였기에 getter에서는 Delete로 변환하여 JSON 데이터로 직렬화하지 못했던 것이었습니다.

 

또한 getter와는 달리 setter에서는 보통 문제가 발생하지 않습니다. JSON 역직렬화 시에는 ObjectMapper가 필드명으로 직접 매칭하기 때문입니다.

 

해결방법

고생했던 원인과 해결방안을 확인했으니 getter 메소드를 직접 구현했습니다.

data class jacksonTest(
    val id : String,
    val name : String,
    val isDelete : String,
    val createdAt : LocalDateTime
)

fun getIsDelete(): String = isDelete

 

 

그동안 코틀린에서 data class와 jackson을 활용해 편리하게 사용하고 있어 몰랐지만 편리한 도구에는 숨겨진 동작과 원리가 있다는 것을 알았고 고생했던 문제였지만 이슈 발생시 다양한 검증과 기본지식이 탄탄해야 한다는 교훈을 다시 한번 얻었습니다. 해결하고 나니 재밌고 유익했습니다 ㅎㅎ

 

 

출처 

https://github.com/FasterXML/jackson

'Kotlin' 카테고리의 다른 글

[Kotlin] Result란?  (0) 2023.09.12
[Kotlin] Error Handling  (0) 2023.08.26
Run Catching  (0) 2023.03.01
[Kotlin] 기초  (0) 2022.08.01