안녕하세요, 하원입니다.
오늘은 JSON의 다양한 파싱 오류에 대해 소개해 보겠습니다.
JSON이란?
보통 제가 프로젝트에서 API를 제작할 때, Request와 Response DTO를 만듭니다. 하지만 프로젝트 초반에 JSON 파싱과 관련된 다양한 오류가 발생한 적이 있었습니다..!
어떤 오류가 왜 발생했는지 소개해 보겠습니다.
com.fasterxml.jackson.databind.exc.InvalidDefinitionException or
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class
Request 또는 Response DTO에 @Getter 지정해 주기.
원인
- 해당 오류는 Jackson에서 data binding이 안되는 오류입니다.
- 제가 Request 또는 Response DTO를 생성했을 때
@Getter를 지정해 주지 않아 발생했었습니다.- private으로 선언된 필드에 접근하려면
@Getter와 같은 접근 메서드가 필요합니다.
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of Request (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)]
@NoArgsConstructor 지정해 주기. // 기본 생성자
public requestDto() {
}
원인
- 해당 오류는 DTO 클래스에 기본 생성자가 없기 때문에 발생하는 오류입니다.
- Jackson 라이브러리가
@RequestBody로 들어온 요청 Body를 객체로 변환시킬 때, 기본 생성자를 필요로 합니다.- 따라서 파라미터로 구성된 생성자가 있다고 해도, 기본 생성자가 없으면 Jackson이 객체를 인스턴스화 할 수 없습니다.
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of KakaoUserInfoResponse$KakaoProfile: non-static inner classes like this can only by instantiated using default, no-argument constructor
// 오류가 발생했던 코드
@Getter
@NoArgsConstructor
public class KakaoUserInfoResponse {
private Long id;
private String connected_at;
private KakaoAccount kakao_account;
// Inner Class #1
@Getter
public class KakaoAccount {
private Boolean profile_needs_agreement;
private KakaoProfile profile;
private Boolean email_needs_agreement;
private String email;
}
// Inner Class #2
@Getter
public class KakaoProfile {
private String nickname;
private String thumbnail_image_url;
private String profile_image_url;
private Boolean is_default_image;
private Boolean is_default_nickname;
}
}
원인
- 해당 오류는 Jackson 라이브러리가 Inner Class인
KakaoAccount와KakaoProfile을 인스턴스화 하지 못해서 발생한 것입니다.non-static inner class로 정의되어 있기 때문에 Inner Class들을 static으로 선언하면 인스턴스화 할 수 있게 됩니다.- 또는 Inner Class들을 외부 별도의 클래스로 분리하면 Jackson이 정상적으로 인스턴스화 할 수 있게 됩니다.
@Getter
@AllArgsConstructor
public class LoginRequestDto {
@JsonProperty("email")
private String email;
@JsonProperty("password")
private String password;
}
@JsonProperty("fieldName")을 DTO의 필드에 지정해 주기.원인
- Lombok과 Jackson을 사용하면 주의해야 할 몇 가지 필드 이름 규칙이 존재했습니다.
- Lombok의
@Getter는 get 이후에 나오는 필드명의 앞 글자를 대문자로 변경하여 메서드 이름을 만듭니다.- Jackson 라이브러리는 get 이후에 나오는 필드명 맨 앞 글자 1개를 소문자로 변경합니다. 하지만 여기서 맨 앞의 두 글자가 모두 대문자라면 이어진 대문자를 모두 소문자로 변경합니다.
- ex)
aName-> Lombok ->getAName-> Jackson ->aname
-> 이렇게aName이라는 필드가 Lombok과 Jackson을 거치면aname으로 바뀌게 됩니다.
-> 전혀 다른 필드명이 되어 매핑되지 않게 됩니다.
JSON 파싱 오류
JSON의 다양한 파싱 오류를 한번 겪어보고 나니, DTO를 만들 때 이제는 습관적으로 필요한 어노테이션들을 지정합니다.
그래도 다른 라이브러리들의 오류보다는 직관적이고 해결하기 쉬운 편이라서 저를 지치게 하지는 않았던 것 같습니다.
하지만 JSON을 자주 접하는 만큼 어떤 원리로 파싱되고, 어떻게 사용해야 하는지 정도는 알 필요가 있다고 생각합니다.
마무리
JSON은 API 작업을 하면서 항상 보기에 친근한 느낌이 든다.
하지만 Request와 Response 등을 전달하는 가장 중요한 역할을 한다고 생각하기 때문에,
JSON에 대한 기초 지식은 필수적으로 학습해야 한다고 생각한다.