HttpMessageNotReadableException에러

김명후·2023년 9월 3일
0

1. 에러내용

[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)

2. 그 원인

이제 입사 5개월차.. 신입으로서 가장 중요한 것은 기본기라는 말에 수긍하여, 그 동안 기본기 관련 책들만 주구장창 읽다가 오랜만에 사이드 프로젝트를 진행하면서, 많은 게 낯설어 에러 파악하는 것조차도 너무 오래걸렸다.

추정원인은 Gson 라이브러리를 사용하여 String을 만들 때,LocalDateTime 객체가 있는 클래스를 Json String으로 변환하게 되면, 정상적으로 변환되지 않는다는 것이다.

  • 관련 에러 메시지
    [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) at [Source: (PushbackInputStream); line: 1, column: 69] (through reference chain: com.example.myounghoosite.data.dto.BoardDto["regDate"])]

3. 해결 시도

향로님 블로그를 발견하였다. 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);

3.1 수정한 테스트 코드

@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));
    }
}

0개의 댓글