1. 문제 상황 (The Problem)
프론트에서 JSON으로 분명히 true를 보냈는데, 서버 DTO에서는 자꾸 false만 찍히는 기괴한 현상.
- JSON: {"isTermsAgreed": true}
- Java DTO: private boolean isTermsAgreed; (Lombok @Getter 사용)
- Log: isTermsAgreed=false (하...)
2. 원인 분석 (Why?)
범인은 자바의 Bean 규약과 Jackson 라이브러리의 지나친 친절함 때문입니다.
- 자바의 관례: 일반적인 필드는 getFieldName()이지만, boolean 타입은 게터 이름이 isFieldName()이 됩니다.
- Lombok의 동작: 필드명이 isTermsAgreed라면, 롬복은 게터를 getIsTermsAgreed()가 아니라 그냥 isTermsAgreed()로 만듭니다.
- Jackson의 오해: JSON을 객체로 바꿀 때 Jackson은 게터 이름에서 is를 떼고 남은 이름을 키값으로 예상합니다.
- isTermsAgreed() 게터를 보고 "아, 이 필드의 JSON 키는 termsAgreed겠구나!"라고 판단해버립니다.
- 하지만 프론트는 isTermsAgreed로 보냈으니, 이름이 안 맞아서 매핑에 실패하고 boolean의 기본값인 false가 박히는 겁니다.
잠깐만! 여기서 Jackson이란?
더보기
Jackson이란?
Jackson(잭슨)은 한마디로 "자바 객체와 JSON 사이를 이어주는 통역사"입니다.
📦 Jackson이 하는 일 (핵심 기능)
스프링 부트(Spring Boot)를 쓰면 기본적으로 내장되어 있는 라이브러리인데, 크게 두 가지 마법을 부립니다.
- 직렬화 (Serialization):
- 자바 객체(DTO, Entity 등)를 JSON 문자열로 변환해서 웹 브라우저나 프론트엔드에 던져주는 역할.
- 예: User 객체 ➡️ {"name": "재원", "age": 28}
- 역직렬화 (Deserialization):
- 프론트엔드에서 보낸 JSON 데이터를 받아서 자바 객체(DTO)에 쏙쏙 꽂아주는 역할.
- 예: {"email": "..."} ➡️ SignupInitiateRequest 객체 생성
- 문제가 발생한 지점이 바로 여기입니다! 잭슨이 JSON 키값을 자바 필드에 꽂아넣으려다 이름 규칙 때문에 실패한 거죠.
⚠️ 꼭 기억해야 할 잭슨의 특성
잭슨은 아주 깐깐한 규칙을 가지고 데이터를 매핑합니다.
- 기본 생성자 필수: 잭슨이 객체를 만들 때 일단 빈 객체를 생성해야 해서 @NoArgsConstructor가 꼭 필요합니다.
- Getter/Setter 기반: 필드에 직접 접근하는 게 아니라, 게터나 세터 메서드 이름을 보고 JSON 키값을 유추합니다.
- 그래서 isTermsAgreed 필드의 게터인 isTermsAgreed()를 보고 잭슨은 "아, 이놈 키값은 termsAgreed구나!"라고 (지 맘대로) 오해를 했던 겁니다.
3. 해결책: @JsonProperty (The Solution)
이름 규칙이고 나발이고, "닥치고 이 키값으로 매핑해!"라고 강제로 지정해주는 것이 가장 확실하고 깔끔합니다.
✅ 수정된 DTO 코드
@Getter
@NoArgsConstructor
@ToString
public class SignupInitiateRequest {
@JsonProperty("isTermsAgreed") // JSON 키값을 명시적으로 지정!
private boolean isTermsAgreed; // 이제 자바 변수명에 is를 붙여도 OK
@JsonProperty("isPrivacyAgreed")
private boolean isPrivacyAgreed;
}
💡 정리 및 교훈
- 자바에서 boolean 필드에 is를 붙여서 선언하면 라이브러리(Lombok, Jackson) 간에 이름 해석이 꼬일 확률이 매우 높다.
- 가장 속 편한 방법은 @JsonProperty("원하는키값")을 사용하는 것이다.
- 이걸 모르면 멀쩡한 코드를 두고 애꿎은 프론트엔드나 네트워크 설정만 뒤지게 된다. (내 시간...)
반응형
'백엔드 > 🍃 SpringBoot' 카테고리의 다른 글
| [JPA] @OneToMany, @ManyToOne 초간단 정리 (0) | 2026.03.03 |
|---|---|
| 배포시 spring-boot-devtools 비활성화 (1) | 2025.06.13 |
| Spring Boot | 패키지 구조(계층형 vs 도메인형) (1) | 2025.05.29 |
| findAll()에서 Optional<List<T>>를 쓰지 않아도 되는 이유 (1) | 2025.05.20 |
| SpringSecurity + JWT 에서 nginx health-check 요구시 문제 (0) | 2025.04.01 |