✅문제 상황
서버에서 날짜와 시간 데이터를 받아와야하는데 타입이 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
어노테이션 붙이지 않아도 잘 보내진다!
참고자료
'Backend > Java' 카테고리의 다른 글
Java - Servlet (1) | 2024.09.01 |
---|---|
JSP와 Servlet (0) | 2023.08.05 |