[Error] API 응답으로 LocalDatetime이 안와요 ㅠㅠ

겔로그·2022년 11월 30일
1

ERROR 모음집

목록 보기
4/4

시도때도 없이 찾아오는 에러... 최근에 잠잠하나 했더니 또 찾아왔습니다..

이번 에러는 api 반환값과 관련된 이야기로 문제 상황 및 해결 방법에 대해 공유하는 시간을 가져보겠습니다.

문제

특정 api를 호출하여 결과값 중 만료 시간이 오늘을 지났는가를 확인하는 로직을 구현하고 있었습니다.
postman을 통해 결과값 응답을 정상적으로 받는 것을 확인한 이후 코드를 구현하였으나, 계속해서 parsing error가 발생하였습니다.

응답 받는 클래스 코드는 다음과 같습니다.

public class FileResponse {
	private int size;
    private String fileName;
    private LocalDatetime startDttm;
    private LocalDatetime expirationDttm;
}

처음에는 parsing error라 하여 RestTemplate 문제인가... 하고 확인하였으나, 기존에 서비스에서 잘 사용하고 있던 코드였기에 머릿속이 하얘져만 갔습니다.
시간이 지나 천천히 디버깅을 진행하였고 다음과 같은 에러 코드를 만나게 되었습니다.

기대한 결과 값

{"startDttm":"2014-11-07T15:06:36.545Z",...}

현실

{"startDttm":{"hour":14,"minute":32,"second":39,"year":2014,"month":"NOVEMBER","dayOfMonth":6,"dayOfWeek":"THURSDAY","dayOfYear":310,"monthValue":11,"nano":0,"chronology":{"calendarType":"iso8601","id":"ISO"}},...}

..?

전 분명히... LocalDatetime을 받았는데요..? 응답 결과가 왜 저런 친구가 나온 것일까요?

발생 원인

우선, 해당 내용을 이해하기 위해서는 Jackson에 대한 이해가 필요했습니다.

Jackson이란?

Jaskson은 Java JSON 라이브러리 중 하나로, Java용 데이터 처리 도구라 생각하시면 이해가 편하실 겁니다.

기본은 JSON 형태의 데이터를 다루기 위해 태어난 친구이지만 수요가 늘면서 현재는 다음과 같은 데이터를 추가로 처리할 수 있게 되었습니다.

인코딩된 프로세스 데이터 처리

  • Avro, BSON, CBOR, CSV, Smile, (Java) Properties, Protobuf, TOML, XML, YAML 등

그래서? 원인이 뭐라고?

사실 Jackson은 LocalDatetime 클래스의 데이터 처리를 지원해주고 있습니다. 하지만 클래스 자체로 API로 응답하는 것은 어려워 요청에 대한 응답 과정에서 LocalDatetime 클래스를 다음과 같이 직렬화 해줍니다.

기본조건으로 직렬화한 LocalDateTime

앞서 보셨던 결과값과 동일한 것을 알 수 있죠? 원인은 바로 응답 과정에서 발생한 직렬화된 LocalDateTime 객체를 실질적으로 받을 때 역직렬화(Deserializable)를 하지 않아 parsing error가 발생한 것이었습니다.

자 그럼 이제 해결하러 가볼까요?

해결 방법

해결 방법에는 여러가지가 있습니다. 지금부터 하나 하나 방법을 공유드리겠습니다.

1. @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")을 붙인다.

사용하는 값에 다음과 같은 어노테이션을 추가합니다. 바로 아래와 같이 말입니다.

public class FileResponse {
	private int size;
    private String fileName;
    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
    private LocalDatetime startDttm;
    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
    private LocalDatetime expirationDttm;
}

다음과 같이 진행할 경우, 개발자는 LocalDatetime 클래스에 원하는 패턴을 지정해 유연하게 값을 얻을 수 있습니다. 하지만 LocalDatetime 클래스를 응답으로 받을 경우 이 어노테이션을 필수적으로 붙여줘야만 오류가 발생하지 않는 단점 또한 존재합니다.

2. ObjectMapper customizing

Spring Boot에서는 싱글턴 패턴으로, @Bean이라는 개념으로 단일 인스턴스를 스프링 컨테이너가 관리(IOC)하며 필요에 알맞게 의존성 주입(DI)을 진행하고 있습니다.

이 때, Spring Boot를 사용하는 ObjectMapper경우 기본적으로 인스턴스가 이미 제공되고 있습니다.
참고: https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#howto.spring-mvc.customize-jackson-objectmapper

우리는 이 ObjectMapper를 이용해 직렬화 문제를 해결할 수 있습니다.
Spring MVC는 HTTP 통신간 내용 교환을 위해 HttpMessageConverters를 이용하고 있습니다. Jaskson이 classpath에 존재할 경우 우리는 default convertor로 Jackson2ObjectMapperBuilder를 이용하게 되는데 이 때 사용하는 것이 ObjectMapper입니다.

따라서~ 요놈만 커스터마이징 해준다면 LocalDatetime에서 직렬화 문제가 발생하지 않는다는 점~을 알 수 있게 됩니다.

수정 방법은 두 가지가 있습니다.

2-1. jackson-datatype-jsr310 의존성 추가 후 yml 설정(JavaTimeModule off)

spring:
  jackson:
    serialization:
      WRITE_DATES_AS_TIMESTAMPS: false

2-2. jackson-datatype-jsr310 의존성 추가 후 ObjectMapper 자체 custom

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

Jackson에는 JavaTimeModule이라는 것이 포함되지 않아 추가로 jackson-datatype-jsr310 의존성 주입을 해줘야만 LocalDatetime에 대한 대응이 가능한 것을 알 수 있게 되었습니다.

내 해결 방안은?

처음에는 1번 썼다가 잘 되는 것을 확인한 뒤, 2번 또한 적용을 해보았습니다.

결과적으로는 굳이 LocalDatetime으로 받아야 되나? String으로 받자로 결론이 도출되어 응답 객체로 LocalDatetime이 아닌 String으로 정의하여 사용하게 되었습니다.

결론

jackson은 json 파싱 라이브러리로 많은 사람들이 사용하고 있습니다.
하지만 spring boot와 jaskson, 두 가지를 사용할 경우 LocalDatetime, LocalDate 등 객체에 대한 return값이 ObjectMapper를 통해 어떻게 들어오는지 결과값을 한 번 확인해보시고 이용하시는 것을 추천드립니다.

Reference

profile
Gelog 나쁜 것만 드려요~

0개의 댓글