JSON으로 자바 DTO 만들기 — API 응답 매핑
API를 호출하면 돌아오는 건 결국 JSON 한 덩어리입니다. 이걸 자바에서 다루려면 같은 모양의 클래스가 필요한데, 그 클래스를 DTO(Data Transfer Object)라고 부릅니다. 손으로 필드를 옮겨 적다 보면 오타·타입 실수가 잦으니, 규칙을 알아 두고 자동 변환을 곁들이면 훨씬 빠릅니다.
DTO가 뭐고 왜 쓰나
DTO는 계층과 계층 사이에서 데이터를 실어 나르는 "전송용 객체"입니다. 컨트롤러가 클라이언트에게 응답을 보내거나, 외부 API의 응답을 받아 올 때 그 모양을 그대로 담는 그릇 역할을 합니다. DB 테이블과 묶인 엔티티를 외부에 직접 노출하면 내부 구조가 새고 필요 없는 필드까지 따라가기 쉬운데, DTO를 따로 두면 밖으로 보일 모양과 안에서 쓰는 모양을 분리할 수 있습니다. 응답 JSON의 키 하나하나가 DTO의 필드 하나에 대응한다고 보면 됩니다.
JSON 값 → 자바 타입 추론
JSON 값의 생김새를 보면 어떤 자바 타입으로 받을지 대략 정해집니다.
| JSON 값 | 예시 | 자바 타입 |
|---|---|---|
| 문자열 | "hong" | String |
| 정수 | 42 | int / long |
| 실수 | 3.14 | double / BigDecimal |
| 불리언 | true | boolean |
| 배열 | [1, 2, 3] | List<Integer> |
| 중첩 객체 | { "addr": {…} } | 별도 DTO 클래스 |
| null | null | 래퍼 타입(Integer 등) |
중첩 객체는 안쪽도 하나의 DTO로 떼어내고, 바깥 DTO가 그 타입의 필드를 갖게 합니다. 배열 안에 객체가 들어 있으면 List<그DTO> 형태가 됩니다. 값이 null로 올 수 있는 자리는 int 대신 Integer 같은 래퍼 타입으로 받아야 NullPointerException을 피합니다.
네이밍과 @JsonProperty
API JSON은 user_name처럼 snake_case로 오는 경우가 많지만, 자바 필드는 userName 같은 camelCase가 관례입니다. 이름이 다른 필드에는 @JsonProperty("user_name")를 붙여 "이 JSON 키를 이 필드에 매핑하라"고 알려 줍니다. 키가 전부 snake_case라면 매핑 전략을 한 번에 SNAKE_CASE로 설정해 애너테이션 없이 자동 변환할 수도 있습니다. 헷갈리기 쉬운 부분이라, JSON → Java DTO 변환 도구로 JSON을 붙여 넣으면 어느 필드에 @JsonProperty가 필요한지까지 채워 줍니다.
Lombok·record로 줄이기
필드가 많으면 getter·setter·생성자가 끝없이 늘어납니다. 이때 두 가지 방법이 흔히 쓰입니다.
- Lombok — 클래스에 @Getter, @Setter, @NoArgsConstructor 같은 애너테이션만 붙이면 반복 코드를 컴파일 시점에 자동 생성합니다.
- record — 값이 바뀌지 않는 불변 응답 DTO라면 record로 한 줄에 필드를 선언해 생성자·getter·equals까지 한꺼번에 얻습니다.
응답 전용이고 수정할 일이 없다면 record가 간결하고, 라이브러리가 setter나 가변성을 요구하면 Lombok 쪽이 무난합니다. 어느 쪽이든 골격은 변환 도구로 뽑고 취향에 맞게 다듬는 흐름이 가장 빠릅니다.
엔티티는 데이터베이스 테이블과 매핑되는 영속성 객체이고, DTO는 계층 사이에서 데이터를 실어 나르는 전송용 객체입니다. API 응답·요청처럼 외부와 주고받는 모양은 DTO로 따로 두는 편이 안전합니다. 엔티티를 그대로 노출하면 DB 구조가 밖으로 새고, 필요 없는 필드까지 직렬화되기 쉽기 때문입니다. 그래서 보통 컨트롤러 경계에서는 DTO를, 영속성 계층에서는 엔티티를 쓰고 둘 사이를 변환합니다.
JSON은 정수와 실수를 모두 number 하나로 표현하므로 값만 보고 정확히 단정하기 어렵습니다. 소수점이 없으면 int 또는 long, 소수점이 있으면 double로 두는 것이 일반적인 추론입니다. 다만 돈처럼 오차가 곤란한 값은 BigDecimal, 아주 큰 식별자는 long이나 String으로 받는 편이 안전합니다. 변환 도구가 제시한 타입을 출발점으로 삼고, 도메인 의미에 맞게 손보면 됩니다.
필드는 자바 관례대로 camelCase로 두고, JSON 키와 이름이 다른 필드에만 @JsonProperty("user_name") 같은 애너테이션으로 매핑을 지정하면 됩니다. 키가 전부 snake_case라면 매핑 전략을 SNAKE_CASE로 한 번에 설정해 애너테이션 없이 자동 변환할 수도 있습니다. 자동 변환 도구는 이 차이를 감지해 필요한 곳에만 @JsonProperty를 붙여 주므로 손으로 일일이 맞추는 수고를 덜 수 있습니다.