[2023-09-02 19:17:39.061] [WARN ] [main] org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.time.LocalDateTime` from Object value (token `JsonToken.START_OBJECT`); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.time.LocalDateTime` from Object value (token `JsonToken.START_OBJECT`)<LF> at [Source: (PushbackInputStream); line: 1, column: 69] (through reference chain: com.example.myounghoosite.data.dto.BoardDto["regDate"])]
MockHttpServletRequest:
HTTP Method = POST
Request URI = /board
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"262"]
Body = {"title":"title","content":"content","boardType":"common","regDate":{"date":{"year":2023,"month":9,"day":2},"time":{"hour":17,"minute":30,"second":0,"nano":0}},"chgDate":{"date":{"year":2023,"month":9,"day":2},"time":{"hour":17,"minute":30,"second":0,"nano":0}}}
Session Attrs = {}
Handler:
Type = com.example.myounghoosite.controller.BoardController
Method = com.example.myounghoosite.controller.BoardController#createBoard(BoardDto)
이제 입사 5개월차.. 신입으로서 가장 중요한 것은 기본기라는 말에 수긍하여, 그 동안 기본기 관련 책들만 주구장창 읽다가 오랜만에 사이드 프로젝트를 진행하면서, 많은 게 낯설어 에러 파악하는 것조차도 너무 오래걸렸다.
추정원인은 Gson
라이브러리를 사용하여 String을 만들 때,LocalDateTime
객체가 있는 클래스를 Json String으로 변환하게 되면, 정상적으로 변환되지 않는다는 것이다.
java.time.LocalDateTime
from Object value (token JsonToken.START_OBJECT
);java.time.LocalDateTime
from Object value (token JsonToken.START_OBJECT
) at [Source: (PushbackInputStream); line: 1, column: 69] (through reference chain: com.example.myounghoosite.data.dto.BoardDto["regDate"])]향로님 블로그를 발견하였다. Entity와 Dto, Dao 모두에 @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
애노테이션을 달아주었다.
하지만 해결되지 않았다. 이유를 찾아보니, Gson 라이브러리를 사용하여 Json String을 만드는 것 때문인 듯하다.
Gson이 담긴 내 코드 일부
Gson gson = new Gson();
String content = gson.toJson(boardDto);
두번째 출처와 같이, config 폴더안에 GsonLocalDateTimeAdapter
클래스를 만들어주고 테스트 코드에 적용하니 해결되었다.
수정한 Gson 코드 일부
Gson gson = new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new GsonLocalDateTimeAdapter())
.registerTypeAdapter(LocalDateTime.class, new GsonLocalDateTimeAdapter()).create();
String content = gson.toJson(boardDto);
@WebMvcTest(BoardController.class)
public class BoardControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
BoardServiceImpl boardService;
private static LocalDateTime t = LocalDateTime.of(2023, 9, 02, 17, 30);
private static LocalDateTime chgT = LocalDateTime.of(2023, 9, 02, 18, 30);
@Test
@DisplayName("Board 데이터 생성 테스트")
void createBoardTest() throws Exception {
given(boardService.saveBoard(new BoardDto("title", "content", "boardType", t, t)))
.willReturn(new BoardResponseDto(123L, "title", "content", "boardType", t, t));
BoardDto boardDto = BoardDto.builder()
.title("title")
.content("content")
.boardType("common")
.regDate(t)
.chgDate(t)
.build();
Gson gson = new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new GsonLocalDateTimeAdapter())
.registerTypeAdapter(LocalDateTime.class, new GsonLocalDateTimeAdapter()).create();
String content = gson.toJson(boardDto);
mockMvc.perform(
post("/board")
.content(content)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.boardId").exists())
.andExpect(jsonPath("$.title").exists())
.andExpect(jsonPath("$.content").exists())
.andExpect(jsonPath("$.boardType").exists())
.andExpect(jsonPath("$.regDate").exists())
.andExpect(jsonPath("$.chgDate").exists())
.andDo(print());
verify(boardService).saveBoard(new BoardDto("title", "content", "boardType", t, chgT));
}
}