[⛔Error] JSON 파싱 오류 해결하기

Ogu·2024년 3월 29일
1

🗂️ 상황

쇼핑몰 v1을 Mybatis로 구현하는 프로젝트에서 Category 생성을 위해 RequestDTO로 POST 요청을 보냈는데 400 Bad Request Error 가 떴습니다.

오류 메시지는 다음과 같습니다.

03-29 14:10:34.937 WARN 2744 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of org.store.clothstar.category.dto.CreateCategoryRequest (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of org.store.clothstar.category.dto.CreateCategoryRequest (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 2, column: 5]]

🗂️ 원인

해당 오류는 HTTP 요청의 본문에서 JSON 데이터를 읽을 때 발생한 문제입니다.

더 구체적으로, com.fasterxml.jackson.databind.exc.MismatchedInputException 오류는 JSON 데이터를 Java 객체로 변환할 때 예상되는 형식과 실제 형식이 일치하지 않아 발생한 것입니다.

🗂️ 해결 방법

근본적인 원인은 jackson library가 기본 생성자가 없는 모델을 생성하는 방법을 모르기 때문입니다.

따라서 요청의 @RequestBody로 넘어오는 DTO 객체에 기본 생성자를 추가해 주거나, 직접 역직렬화할 생성자를 지정해줍니다.

🎨 Jackson

Jackson은 Java에서 객체를 JSON 문자열을 변환(역직렬화)하거나 JSON 문자열을 객체로 변환(직렬화)하는 기능을 제공하는 라이브러리입니다.

Jackson은 기본 생성자를 통해 JSON 데이터를 역직렬화 합니다.
기본적으로는 기본 생성자가 있어야 리플렉션을 통해 객체를 생성합니다.
따라서 객체에 기본 생성자가 없는 경우 Jackson에서 InvalidDefinitionException 예외를 발생시킵니다.

🎨 참고 !!

POST

  • 스프링에서 JSON형 변환을 담당하는 것은 Jackson2HttpMessageConverter
  • @RequestBody 로 JSON 데이터가 넘어오면 Java Object 변환은 Jackson2HttpMessageConverter에서 담당
  • Jackson2HttpMessageConverterObjectMaper를 사용해서 Object로 전환

GET

JSON 데이터가 아닌 Query Parameter를 받음
그래서 Jackson2HttpMessageConverter 가 아니라 WebDataBinder 를 사용

🦕 방법 1. @RequestBody 객체에 @NoArgsConstructor로 public 기본생성자 추가

다음과 같이 @RequestBody로 들어오는 객체에 @NoArgsConstructor 를 추가합니다.

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CreateCategoryRequest {

    @NotBlank(message = "카테고리 타입을 입력해주세요.")
    private String categoryType;

    public Category toCategory() {
        return Category.builder()
                .categoryType(categoryType)
                .build();
    }
}

🎨 @NoArgsConstructor@AllArgsConstructor 순서
@NoArgsConstructor@AllArgsConstructor를 동시에 쓸 경우
@AllArgsConstructor를 먼저 써야 합니다!
또한 Lombok 사용시 주의점은 다음 링크를 참고하세요!

🌠 방법 1의 장점과 단점

장점

  • 간단하고 빠르게 해결할 수 있다.
  • Lombok을 사용하여 코드를 더 간결하게 작성할 수 있다.

단점

  • 생성자가 public으로 노출되기 때문에 외부에서 객체를 생성할 수 있는 보안 이슈가 발생할 수 있다.
  • 객체의 불변성을 보장하지 않는다.

🦕 방법2. protected 기본 생성자 추가

아래와 같이 기본 생성자를 추가합니다.

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CreateCategoryRequest {

    @NotBlank(message = "카테고리 타입을 입력해주세요.")
    private String categoryType;

    protected CreateCategoryRequest() {
  
    }
  
    public Category toCategory() {
        return Category.builder()
                .categoryType(categoryType)
                .build();
    }
}

🌠 방법 2의 장점과 단점

장점

  • 객체의 불변성을 유지할 수 있다.
  • 객체 생성에 대한 제어를 더욱 강화할 수 있다.

단점

  • 코드가 더 복잡해질 수 있다.
  • 코드의 가독성이 낮아질 수 있다.

🦕 방법3. 불변 객체를 위해 final 변수를 사용하고 싶다면, @NoArgsConstructor 없이 @JsonCreator, @JsonProperty 사용

만약 엄격하게 객체를 불변으로 두기 위해서 final을 유지하고 싶다면 @JsonCreator 와 @JsonProperty 애너테이션을 사용하여 final 변수에 값을 주입할 수 있습니다.

@JsonCreator 애너테이션은 Jackson이 역직렬화할 생성자를 지정해 준다.

@JsonProperty 애너테이션은 Jackson이 역직렬화할 JSON 데이터의 이름을 지정해 준다.

 public class BuddyDto {

   @Getter
   @Builder
   public static class PostRecruitment {
       private final String title;
       private final String content;
       private final String travelNationality;
       private final String travelStartDate;
       private final String travelEndDate;

				@JsonCreator
       public PostRecruitment(@JsonProperty("title") String title,
                              @JsonProperty("content") String content,
                              @JsonProperty("travelNationality") String travelNationality,
                              @JsonProperty("travelStartDate") String travelStartDate,
                              @JsonProperty("travelEndDate") String travelEndDate) {
           this.title = title;
           this.content = content;
           this.travelNationality = travelNationality;
           this.travelStartDate = travelStartDate;
           this.travelEndDate = travelEndDate;
       }
   }

		...

}

🌠 방법 3의 장점과 단점

장점

  • 객체의 불변성을 유지하면서도 객체를 역직렬화할 수 있다.
  • 객체 생성에 대한 제어를 더욱 강화할 수 있다.

단점

  • 코드가 더 복잡해질 수 있다.
  • 각 필드에 대한 생성자 매개변수를 지정해주어야 하기 때문에 유지보수가 어려울 수 있다.

결과

🦕 방법 1을 택한 이유

방법 1을 택한 이유는 다음과 같습니다.

  1. 아직 DTO의 각 필드를 final로 강제하고 있지 않다.
  2. Lombok을 사용하여 코드를 더 간결하게 한다.
  3. 공유 및 팀원들과의 컨벤션을 맞추기 편하다.

결과

방법 1을 택하여 적용하였을 때 다음과 같이 JSON 역직렬화가 잘 작동하여 해결된 모습을 볼 수 있습니다.

참고

profile
私はゲームと日本が好きなBackend Developer志望生のOguです🐤🐤

0개의 댓글