Backend/Java

Spring - LocalDateTime으로 원하는 데이터 주고 받기

2-doooo-2 2024. 10. 12. 13:16
728x90
반응형

✅문제 상황 

서버에서 날짜와 시간 데이터를 받아와야하는데 타입이 LocalDateTime이었다. 

프론트에서는 input에 <input type="date"> 으로 날짜만 보내고 있었던 상황 

그래서 다음과 같은 에러가 떴다. 

Resolved [org.springframework.http.converter.HttpMessageNotReadableException: 
JSON parse error: Cannot deserialize value of type `java.time.LocalDateTime` 
from String "2024-09-22 00:00": Failed to deserialize java.time.LocalDateTime: 
(java.time.format.DateTimeParseException) 
Text '2024-09-22 00:00' could not be parsed at index 10]

 

JSON 파싱 오류: 문자열 "2024-09-22 00:00"에서 `java.time.LocalDateTime` 유형의 값을 역직렬화할 수 없다고 한다!! 

 

※역직렬화(Deserialization)란? = @RequestBody

더보기

직렬화된 데이터를 다시 객체의 형태로 만드는 것

 

ex)
class Person{
	private String name;
    
    public Sample(String name) {
        this.name = name;
    }
}

 

위와 같은 클래스가 있다고 할 때, Json 데이터 형식을 예로 들면

Person person = new Person("김철수"); 객체를 { "name" : "김철수"} 와 같은 방식으로 변경하는 것을 직렬화,

{ "name" : "김철수"} 데이터를 받아서 Person이라는 객체의 name 필드에 "김철수" 를 할당하고 객체를 생성하는 것을 역직렬화라고 할 수 있다.

 

✅실패한 방법 

일단 프론트에서 시간까지 같이 보낼 수 있는  type으로 수정

<div>
   <label htmlFor="deadline">서류지원 마감</label>
      <input
        id="deadline"
        type="datetime-local"
        value={deadline}
        onChange={(e) => setDeadline(e.target.value)}
        required
       />
  </div>

 

※날짜를 지정할 수 있는 HTML의 input 요소에서 사용 가능한 type 속성

더보기

date: 날짜를 선택할 수 있는 입력 필드 (년, 월, 일)

<input type="date">

 

datetime-local: 날짜와 시간을 선택할 수 있는 입력 필드 (년, 월, 일, 시간, 분)

<input type="datetime-local">

 

month: 연도와 월만 선택할 수 있는 입력 필드

<input type="month">

 

week: 연도와 주를 선택할 수 있는 입력 필드

<input type="week">

 

time: 시간만 선택할 수 있는 입력 필드 (시간, 분)

<input type="time">

 

스프링 RequestDto에서는 

@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
LocalDateTime deadline,

 

@DateTimeFormat 어노테이션을 추가해주었다. 

이렇게 하면 클라이언트에서 "yyyy-MM-dd HH:mm" 형식의 문자열로 날짜와 시간을 입력받아 LocalDateTime 필드로 변환해 처리할 수 있다. 

하지만!! 똑같은 에러가 발생했다. 

 

✅성공한 방법 

react에서 날짜 보내는 데이터 포맷팅해서 보내기 

   const formatDate = (date) => {
      const d = new Date(date);
      const year = d.getFullYear();
      const month = String(d.getMonth() + 1).padStart(2, '0');
      const day = String(d.getDate()).padStart(2, '0');
      const hours = String(d.getHours()).padStart(2, '0');
      const minutes = String(d.getMinutes()).padStart(2, '0');
      return `${year}-${month}-${day} ${hours}:${minutes}`;
    };

    const formattedDeadline = formatDate(updatedResult.deadline);
    const formattedResultDate = formatDate(updatedResult.result_date);

 

이 값을 body에 담아서 보내주는 것이다. 

body: JSON.stringify({
   company_name: updatedResult.company_name,
   deadline: formattedDeadline,
   result_date: formattedResultDate,
   result: updatedResult.result, 
}),

 

이렇게 포맷팅을 해서 보내야하는 이유는 

HTML5의 datetime-local 입력 형식과 서버의 LocalDateTime 차이 때문이다. 

사용자가 입력한 값은 "yyyy-MM-ddTHH:mm" 형식, 서버에서 LocalDateTime으로 데이터를 받기 위해서는 yyyy-MM-dd HH:mm으로 변환해주는 작업이 필요!!

 

스프링에서 @DateTimeFormat 대신 @JsonFormat 사용하기 

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul")
LocalDateTime deadline,

@DateTimeFormat 대신 @JsonFormat을 사용했더니 에러가 해결됐다 

@DateTimeFormat 대신 @JsonFormat 했더니 에러가 해결된걸까? 

 

 

@DateTimeFormat  vs @JsonFormat 

@DateTimeFormat 

package org.springframework.format.annotation;

...

public @interface DateTimeFormat {
     ...
 }

패키지에서 볼 수 있듯이 @DateTimeFormat은 Spring의 어노테이션이다. 

 

@JsonFormat 

package com.fasterxml.jackson.annotation;

...

public @interface JsonFormat {
	...
 }

 

패키지를 보면 @JsonFormat @RequestBody, @ResponseBody를 사용한 직렬화/역직렬화를 담당하는

Jackson 라이브러리의 어노테이션이다. 

 

Jackson 라이브러리에서는 내부적으로 pattern으로 지정한 형식을 LocalDateTime으로 변환해준다.

따라서, Jackson 라이브러리의 @JsonFormat은 날짜 형식이라면 자유롭게 사용이 가능하다. 

 

하지만, @RequestBody, @ResponseBody를 사용한 역직렬화/직렬화 시

@DateTimeFormat을 사용하려면 Jackson 라이브러리에 있는 어노테이션이 아니기 때문에

LocalDateTime의 기본 형식인 'yyyy-MM-dd'T'HH:mm:ss'으로 요청이 들어와야 바인딩된다. 

(이때, @DateTimeFormat을 사용하지 않아도 바인딩이 된다)

 

결론적으로 @RequestBody, @ResponseBody를 사용한 역직렬화/직렬화 시

날짜 형식을 자유롭게 사용하기 위해서는

@DateTimeFormat이 아닌, @JsonFormat을 사용해야한다. 

 

(물론, Request Body 날짜 형식이 LocalDateTime의 기본 형식인  'yyyy-MM-dd'T'HH:mm:ss'이라면

@DateTimeFormat, @JsonFormat 둘다 사용하지 않아도 그대로 바인딩이 가능하다.)

 

✅하지만!!! 진짜 성공한 방법 

LocalDateTime의 기본 형식인 'yyyy-MM-dd'T'HH:mm:ss'이 아닐때를 전제

1. @RequestBody, @ResponseBody : @JsonFormat 사용
2. @RequestParam, @ModelAttribute : @DateTimeFormat 사용

 

블로그에서 이 글을 보고 오잉?? 싶었다. 내가 보내는 데이터는 <input type="datetime-local"> 이 형식을 사용했다면 기본형식이  'yyyy-MM-dd'T'HH:mm:ss' 이게 맞을 텐데,,, 왠걸!! 포맷팅도 안해주고 어노테이션을 붙이지 않아도 제대로 보내진다.. 이게 무슨일이지??? 고치는 과정에서 어노테이션을 추가하고 테스트를 해봐서 그런가? 흑흑

꼭 저 전제를 잊으면 안될 것 같다!! <input type="datetime-local">  을 사용했다면 @JsonFormat , @DateTimeFormat  이 어노테이션을 붙이지 않아도 LocalDateTime 타입에 맞춰서 보내진다! 

 

최종 코드 

<input type="datetime-local"/>

 

이렇게만 바꿔주고

LocalDateTime deadline

 

어노테이션 붙이지 않아도 잘 보내진다!

 

 

 

 

참고자료

728x90
반응형