티스토리 뷰

클라이언트에서 전달받은 날짜 포맷의 데이터를 @JsonFormat을 이용하여 ZonedDateTime 타입의 변수에 바인딩할 때주의할 점에 대해 알아보겠습니다.

 

일단 클라이언트에서 전달받은 날짜 포맷의 데이터를 ZoneDateTime 타입으로 받을 때 UTC로 변환되는 이슈가 있었는데요.  예제코드 (1)을 통해 함께 알아보겠습니다.

 

예제코드 (1)

ZonedDateTime 타입의 필드를 가진 Product 클래스가 있고 이 필드 위에는 다음과 같은 어노테이션이 선언돼있습니다.

위 예제에서의 @JsonFormat 역할은 "yyyy-MM-dd HH:mm:ss" 패턴의 String 데이터ZonedDateTime 타입으로 역직렬화를 해주는 코드이며 Timezone은 Asia/Seoul로 세팅하는 코드입니다.

 

 

그리고 해당 데이터를 출력하는 코드입니다.

 

 

1
curl -X POST "http://localhost:8080" -H "Content-Type:application/json" -d "{\"startDate\":\"2023-04-01 00:00:00\"}"
cs

이제 위 요청정보로 요청해보겠습니다.

 

 

2023-04-01 00:00:00으로 데이터를 전송했지만 Timezone이 UTC로 변경된 걸 확인하실 수 있습니다.

이제 왜 UTC로 변환이 되어졌는지 확인해보겠습니다.

 

원인분석

이 상수는 com.fasterxml.jackson.databind.DeserializationFeature Enum에 정의돼있는 상수입니다. 

DeserializationFeature은 직렬화된 데이터를 Java 객체로 역직렬화 해주는 역할을 합니다. 

그리고 위와 같은 상수가 정의되어 있습니다.

 

간단히 설명해 드리자면 이 옵션이 활성화 되었을 경우 MessageConverter에 등록된 Timezone 정보를 기반으로

날짜 데이터를 세팅합니다.

 

정확히는 MessageConverter는 내부적으로 ObjectMapper를 이용해서 직렬화 / 역직렬화를 진행하는데

ObjectMapper에 Timezone 정보가 들어가 있습니다.

 

역직렬화는 하는 과정 중에 com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer 클래스의 _fromString 메서드를 호출합니다. 이때 value 변수의 값을 확인해보면 우리가 원하는 값이 들어가 있는 걸 확인하실 수 있습니다.

 

근데 여기서 shouldAdjustToContextTimezone 메서드의 반환 값이 true일 경우 value를 반환하지 않고 다른 값을 반환하는데요. 내부 로직이 어떻게 돼 있는지 확인해보겠습니다.

 

 

 

_adjustToContextTZOverride 변수가 null인지 아닌지에 따라 반환 값이 다른데요. 지금은 null이 저장돼있으니 DeserializationFeature Enum의 ADJUST_DATES_TO_CONTEXT_TIME_ZONE값이 활성화 돼있는지 여부를 반환합니다. 기본값은 true이므로 true가 반환됩니다.

 

 

 

shouldAdjustToContextTimezone 메서드의 반환 값이 true이면 내부로직에서 위 메서드를 실행합니다.

ZoneId를 조회해오기 위함인데요. 더 살펴보겠습니다.

 

 

 

_timezone 변수가 null이 들어가 있으니 DEFAULT_TIMEZONE이 반환됩니다.

 

 

 

DEFAULT_TIMEZONE은 UTC로 지정돼있습니다.

 

즉, UTC로 변환되는 원인은 ObjectMapper에 Timezone이 설정되지 않았거나 혹은 ADJUST_DATES_TO_CONTEXT_TIME_ZONE 값이 true이기 때문에 발생하였습니다.

 

이제 문제를 해결해보도록 하겠습니다.

 

문제해결

위 상수의 기본값을 false로 변경해보겠습니다.

 

 

 

이번에는 해당 if 블록 내부의 코드가 실행되지 않고 그냥 value를 반환합니다.

 

 

 

원하는 값이 출력되었습니다.

 

 

 

이제 ObjectMapper의 Timezone을 Asia/Seoul로 지정해보겠습니다.

 

 

 

아까는 Timezone을 지정하지 않아서 DEFAULT_TIMEZONE이 반환됐지만 지금은 지정된 Timezone이 반환됐습니다.

 

 

 

원하는 값이 출력되었습니다.

 

 

예제코드 (2)

위 예제코드 (1)에서 @JsonFormat의 timezone 속성이 Asia/Seoul로 되어있었는데요. 이번엔 제거해보겠습니다.

 

 

 

그러면 예외가 발생하는데요. 함께 살펴보겠습니다. 이 예외는 spring.jackson.time-zone 설정여부와 무관합니다.

 

 

 

일단 예외가 발생하는 부분은 여기입니다. ZoneId가 null인 건데요. 이 부분은 @JsonFormat에 정답이 나와 있습니다.

 

 

 

tzStr 변수에 ##default라는 값이 있습니다. 그리고 DEFAULT_TIMEZONE과 값이 같으면 null을 반환합니다.

 

 

 

DEFAULT_TIMEZONE 역시 ##default 가 저장돼있습니다.

즉, @JsonFormat에 timezone 속성을 명시하지 않으면 timezone 변수에 null이 할당됩니다.

 

 

의문점

@JsonFormat의 timezone 속성이 지정되지 않았으면 직렬화할 때도 문제가 발생해야 할 것 같은데 직렬화할 때는 문제가 발생하지 않습니다.  왜 그럴까요? 이 문제 역시 디버깅을 통해 코드로 확인하실 수 있습니다.

 

 

InstantSerializerBase 클래스의 formatValue 메서드 내부에서 Zone이 null일 경우 그냥 값을 반환해버립니다.

 

 

 

정리

직렬화 과정에서 timezone이 지정되있지 않는 경우

- 현재 값을 그대로 반환합니다. ( 현재날짜를 반환 시 JVM Option값 'user.timezone' 을 참조합니다.)

 

직렬화 과정에서 timezone이 지정되있는 경우

- 우선순위는 @JsonFormat의 timezone 값, application.yaml의 spring.jackson.time-zone 값 순으로 적용됩니다.

 

역직렬화 과정에서 timezone이 지정되있지 않는 경우

- JSON Parse 예외가 발생됩니다.

 

역직렬화 과정에서 timezone이 지정되있는 경우

- DeserializationFeature의 ADJUST_DATES_TO_CONTEXT_TIME_ZONE 값이 true면 지정된 timezone을 무시하고 UTC로 변경됩니다.

- DeserializationFeature의 ADJUST_DATES_TO_CONTEXT_TIME_ZONE 값이 false이면 그대로 반환됩니다.

- application.yaml의 spring.jackson.time-zone에 정의된 zone에 맞게 변경됩니다.

 

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함