
사이드 프로젝트 수행 중, 팀의 FE 개발자 분이 디스코드로 메세지를 주셨다.
JSON 직렬화 이슈로 3가지의 에러를 마주했고 해결 과정을 기록하고자 포스팅하게 되었다.
Object Mapper란?
위 과정을 수행할 때 사용하는 JackSon의 라이브러리 클래스다.
ObjectMapper가 객체를 역/직렬화 할 때 기본 생성자를 사용한다.
따라서, 요청 혹은 응답 DTO 내부에 기본생성자가 존재하지 않으면, deletegate 또는 property 기반의 생성자를 찾는데 이게 없기 때문에 위 에러를 뱉는 것이다. (delegate, property 부분은 추가적으로 공부해봐야겠다)
{
success: false,
code: 400,
data: [ ],
errors: {
message: "Could not read JSON:Cannot construct instance of `dailymissionproject.demo.domain.mission.dto.response.MissionResponseDto` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (byte[])"{"@class":"dailymissionproject.demo.domain.mission.dto.response.MissionResponseDto","title":"string","content":"string","imgUrl"
"[2024,8,16],"endDate"[truncated 14 bytes]; line: 1, column: 85] "
},
위 ObjectMapper 설명을 통해 기본 생성자가 없기 때문에 발생한 것이다.
응답 객체의 경우 불변 객체로 선언해두었기에, 기본 생성자를 만들어주는 @NoargsConstructor 어노테이션에 강제 옵션을 추가해서 첫 번째 에러를 해결했다.

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion
Jackson에서 양방향 관계로 맺어진 객체는 무한 재귀가 발생하는 문제가 있는데,
Mission과 MissionRule은 1:1관계로 서로 순환 참조를 하고 있다. cascade옵션도 걸려있는 상태다.
따라서 객체가 Json으로 직렬화하는 과정에서 Mission이 변환하면서 연관 관계로 매핑되어 있는 MissionRule의 참조를 수행하고, 다시 MissionRule은 Mission을 참조하는 Infinite Recursion에 빠지는 것이다.
이를 해결하기 위해서는, 양방향 매핑을 선언한 필드에 직렬화와 관련한 어노테이션을 선언해주어야 한다.
@JsonManagedReference

@JsonBackReference

해결이 될 줄 알았는데, 아래 에러를 마주했다..😭

Mission과 MissionRule은 1:1의 관계다. 다음 Mission의 코드를 보면 Lazy 로딩 옵션이 걸려있다.
Mission

문제는 미션 상세 조회 요청이 들어왔을 때, 아래 MissionResponseDTO를 통해 정보를 반환하려 할 때 발생한다.
필드를 통해 볼 수 있듯, MissionRule 도메인을 직접 반환하는 문제가 있다..
Entity를 직접 반환하기보다, DTO로 변환하여 반환시켜야한다.
MissionResponseDto

MissionService

MissionResponseDto 객체를 생성하면서 MissionRule를 초기화하지 않는다.
➡️ FetchType.Lazy 옵션이기 때문에,
실제 MissionRule이 아닌 Proxy$G9BGsCil[\hibernateLazyInitializer] 가 담겨있다.
따라서, 해당 Proxy를 Serialize 시키려고 하니 에러가 발생한 것이다!
MissionRuleResponseDto

of 정적팩토리 메서드를 사용했다.

DTO 내부에서 MissionRule을 반환하면서 초기화를 수행하는 방식으로 리팩토링했다.

2번째 문제 이유인 엔티티 간 양방향 순환 참조는 3번의 에러를 해결하는 과정에서, 엔티티가 아닌 DTO 초기화로 방향성을 잡았기 때문에, @Json 어노테이션을 사용하지 않아도 됩니다.