❗️문제 상황
✉️ 오류 메시지
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `com.example.aws.dto.CheckIdDTO` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)]
📃 문제 코드
// DTO 객체
@Data
@AllArgsConstructor
public class LoginDTO {
private String memberId;
private String password;
}
// ....
//
// 컨트롤러
@PostMapping("/login")
public ResponseEntity<Object> login(@RequestBody LoginDTO loginDto) {
memberService.login(loginDto.getMemberId(), loginDto.getPassword());
return ResponseEntity.ok("LOGIN_SUCCESS");
}
{
"memberId": "ccc",
"password": 1234
}
위와 같이 DTO 객체의 필드가 여러 개일 때는 정상적으로 DTO 객체에 매핑이 되지만,
// DTO 객체
@Data
@AllArgsConstructor
public class CheckIdDTO {
private String memberId;
}
// ...
// 컨트롤러
@PostMapping("/checkIdDuplicate")
public ResponseEntity<Object> checkIdDuplicate(@RequestBody CheckIdDTO checkIdDTO) {
String memberId = checkIdDTO.getMemberId();
if(memberService.checkIdDuplicate(memberId))
throw new MemberIdDuplicateException("중복된 아이디 입니다.");
return ResponseEntity.ok("USABLE_ID");
}
{
"memberId": "ccc"
}
위와 같이 DTO 객체의 필드가 한 개일 때는 위에서 적었던 오류메시지가 발생하며
아래와 같이 400 BadRequest가 발생한다.
💡 해결 방법
🤔 오류가 발생한 이유
그냥 기본 생성자만 추가하면 해결되는건 쉽게 찾을 수 있었다.
근데 내가 궁금한건 왜 객체의 필드가 여러 개일 때는 기본 생성자가 없어도 잘만 매핑이 되면서
객체의 필드가 한개 일 때는 또 안되는가였다.
찾아보다가 아래 블로그 내용을 발견했다.
JSON 필드 하나면 왜 Jackson 파업함?
이번 장바구니 미션 2단계 를 진행하면서, 도저히 이해할 수 없는 에러가 하나 생겼었는데 왜 이러한 에러가 생겼고, 어떻게 해결 했는지 기록을 남겨보려 한다. 예외 발생 user 별로 장바구니를
kong-dev.tistory.com
해당 블로그에서 정리하신 내용을 이야기하면
Jackson이 Json을 객체로 Deserialize할 때 Delegating 방식과 Properites 방식을 사용하는데 필드가 하나이면 Jackson이 어떤 방식인지 알지 못해 발생하는 오류라고 한다.
Delegating vs Properties
- Delegating: JSON의 전체 객체를 생성자의 단일 파라미터로 넘기는 방식.
예를 들어, String이나 Map과 같은 타입으로 JSON을 통째로 넘길 수 있음 - Properites: JSON의 각 필드를 생성자의 각 파라미터에 매핑하는 방식.
각 필드가 개별적으로 매핑되기 때문에, 생성자 파라미터가 여러 개일 경우에 주로 사용.
여러 개일 때는 고민할 필요도 없이 Properites방식을 선택하기 때문에 문제가 없지만
한 개일 때는 Delegating방식을 통해 Json자체를 해당 하나의 필드에 넘겨야 할지,
Properites방식을 통해 단순히 값 매핑만 하면 될지 모호하다고 판단했기 때문에 오류가 발생하는 것이다.
🛠️ 수정
해결하는 방법은 여러 가지가 있다.
Jackson은 Json을 객체로 Deserialize하는데 기본적으로 생성자를 사용한다. 그래서 다음과 같은 순서로 동작한다.
- 기본생성자가 있다면 이를 통해 객체를 만들고 값을 매핑한다.
- 기본 생성자가 아닌 다른 생성자만 있다면 Jackson은 Json과 객체 매핑을 시도한다.
- 그런데 만약 여기서 필드가 한개라면 Jackson은 Delegating 방식인지 Properites방식인지 헷갈려하기 때문에 오류
➡️ 이 경우, 기본 생성자가 아닌 다른 생성자만 있다면 @JsonCreator가 붙어있는 생성자를 찾아 매핑한다. - 여러 개면 Properites 방식이라고 판단하고 잘 넣어줌
- 그런데 만약 여기서 필드가 한개라면 Jackson은 Delegating 방식인지 Properites방식인지 헷갈려하기 때문에 오류
이를 토대로 객체의 필드가 1개일 때 오류를 피하려면 다음과 같은 방식을 취할 수 있다.
1. 기본 생성자
@Data
@AllArgsConstructor
@NoArgsConstructor // 롬복을 통해 기본생성자 추가!
public class CheckIdDTO {
private String memberId;
}
2. 생성자에 @JsonCreator 추가
이 경우엔 생성자가 클래스내에 존재해야하기 때문에 롬복을 활용할 수 없다.
@Data
public class CheckIdDTO {
private String memberId;
@JsonCreator
public CheckIdDTO(@JsonProperty("memberId") String memberId) {
this.memberId = memberId;
}
}
- @JsonProperty의 경우 JSON 상의 필드 명과 객체 내의 필드 명이 같은 경우, 생략이 가능하다.
기본 생성자를 만들면 안되는 경우는 2번, 롬복을 통해 코드를 더 깔끔하게 짜고 싶으면 1번을 택하면 될듯하다.
스프링과 직접적으로 연결된 기술이지만 스프링으로 서버개발을 하면서 충분히 마주칠 수 있는 오류인 것 같아 해당 탭에 정리하였다.