[개발일지] 230531 직렬화 중 만난 Jackson 버전 문제

오늘은 응답 객체 직렬화 관련해서 고생을 했는데 그 과정을 짧게나마 기록해 본다.

 

간단하게 문제상황을 설명하면 market의 open 여부를 확인하는 api를 get 요청했을 때 market의 open 여부에 따라 true, false 값을 반환하는 api인데 동일한 응답값이 두 개가 나오는 문제가 발생했다.

 

필드명이 isMarketOpen이라 치면 아래와 같이 두 개가 중복돼서 나오는 문제가 발생했다.

 

{
	"isMarketOpen":false,
	"marketOpen":false
}

 

이건 누가 봐도 dto객체의 getter 메서드에 문제가 있기 때문에 발생한 문제라고 생각하고 dto객체를 살펴보는데 별 이상 없어 보였다..

 

아래는 문제의 dto객체이다.

 

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public record MarketStatusResponse(boolean isMarketOpen) {
    public static MarketStatusResponse from(boolean marketStatus) {
        return new MarketStatusResponse(marketStatus);
    }
}

 

생성자, getter, equals 등 자동으로 생성해줘서 편리한 record를 사용해서 dto객체를 만들었고  @JsonAutoDetect 어노테이션을 이용해 모든 필드가 직렬화 대상임을 명시해 줬다.

 

요리보고 조리 봐도 이상한 곳이 없는데 응답값이 2개로 직렬화되니 다 뒤집고 집 가고 싶었다..(이렇게 성격파탄자가 되는 건가..)

 

일단 침착하게(?) 컴파일된 파일을 확인하는데 컴파일된 클래스에도 정상적으로 getter 메서드인 isMarketOpen() 하나만 생성되어 있었다. 

 

컴파일된 파일

 

뭐가 문제인 건지 감이 잡히지 않아 한참을 나와 비슷한 사례가 있는지 스택오버플로우나 구글을 열심히 뒤져봤는데 없었다.. 

 

필드에 @JsonProperty 어노테이션 사용하면 문제를 해결할 수 있었지만 문제의 근본적인 해결책이 아니라 일단 보류했다.

 

그래서 문제상황을 구현하기 위해 새 프로젝트를 하나 생성해서 똑같은 코드를 만들어서 요청해 보는데 새 프로젝트에서는 정상적으로 응답값 하나만 반환했다. 

 

 

왜?? 새로 판 프로젝트와 기존 프로젝트의 차이가 뭔데 동일한 코드에서 다른 결괏값을 반환하는 거지??????????? 황당 그 자체..

 

뭐가 다를까 뭐가 다를까 뭐가 다를까 한참을 또 고민을 하다 프로젝트 새로 생성할 때 별 고민 없이 생성했던 spring boot 버전이 떠올랐다. 설마 하고 기존 프로젝트의 spring boot 버전을 확인하는데 2.4.2 버전을 사용하고 있었고 내가 생성한 프로젝트는 3.1.0 버전을 사용하고 있었다.

 

혹시 몰라 새로 만든 프로젝트의 버전을 2.4.2로 낮추고 테스트해 보는데 문제상황이 구현이 된 것이었다. 

문제상황을 만들었는데 기쁜 건... ㅋㅋㅋㅋㅋㅋㅋ

 

일단 아무튼 spring boot 버전과 관련이 있다는 단서를 찾았다는 사실이 좋았다.

 

Jackson 버전 차이

spring boot 버전 차이가 직렬화와 관련이 있는 jackson 라이브러리의 버전에도 차이가 있다는 것을 알게 되었고 jackson 버전이 문제겠구나 어느 정도 확신을 하고 jackson의 버전을 확인했다.

 

spring boot 2.4.2의 jackson 라이브러리 버전은 2.11.4이고 spring boot 3.1.0의 jackson 라이브러리 버전 2.15.0이었다.

 

우선 현 프로젝트(spring boot 2.4.2)에서 문제가 발생한 이유는 record와 @JsonAutoDetect를 혼용해서 사용한 문제였다.

 

@JsonAutoDetect어노테이션은 getter 메서드가 없는 일반 클래스에서 @JsonAutoDetect어노테이션을 사용하면 getter 메서드가 없어도 직렬화 대상을 지정할 수 있다.

 

부끄럽게도 @JsonAutoDetect에 대해서는 이전에 포스팅한 바가 있다..

https://seungjjun.tistory.com/267

 

 

문제 원인

record와 @JsonAutoDetect을 같이 사용해서 문제가 발생한 이유는 아래와 같다.

 

컴파일 시점의 클래스 파일을 보면 record가 생성한 isMarketOpen()이라는 getter 메서드가 존재하기 때문에 해당 필드를 직렬화(marketOpen으로 직렬화) 시켰고, @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)로 인해 isMarketOpen 필드가(isMarketOpen으로 직렬화) 직렬화돼서 응답값이 두 개가 나오게 된 것이었다.

 

record가 생성해주는 getter 메서드가 있는데 왜 @JsonAutoDetect 어노테이션까지 사용하는지 궁금할 수 있는데 이것도 spring boot 버전이 낮아서 record의 getter 메서드를 인식하지 못하는 문제가 있어 @JsonAutoDetect를 사용했다.

 

그러면 dto를 class로 만들면 되지 왜 굳이 record를 사용하는지 궁금할 수 있는데 그냥 record 사용해보고 싶었던거 같기도 하고... 뭐든 새로나온건 좋으니까..

 

그러면 왜 jackson 2.12.0 버전에서는 응답값이 정상적으로 하나만 나오는 것인지 궁금할 수 있는데 ㅋㅋㅋㅋㅋㅋㅋ 아래는 나의 추측이다.

 

결론

jackson 2.12.0 이후 버전부터는 중복된 getter 메서드를 제거하기 위해 jackson이 개선되었다고 한다. 그래서 응답값이 정상적으로 하나만 나오는 이유를 추측하면 record로 인해 생성된 isMarketOpen() getter 메서드가 존재하기 때문에 @JsonAutoDetect 어노테이션이 isMarketOpen 필드를 직렬화하지 않아도 된다는 것으로 판단해서 직렬화하지 않은 것으로 생각을 했다.

 

아무튼 회사 프로젝트의 spring boot 버전을 마음대로 변경할 수 없으니 @JsonProperty를 써서 해결하는 걸로...