자바 객체 ↔ JSON 변환의 마법
JS 객체 표기법에서 출발한 가벼운 텍스트 데이터 형식. 모든 언어가 읽고 쓸 수 있어 — 시스템 사이의 「공용어」.
| 타입 | 예시 |
|---|---|
| 문자열 | "홍길동" |
| 숫자 | 42, 3.14 |
| 불린 | true, false |
| null | null |
| 배열 | [1, 2, "a"] |
| 객체 | {"key": "value"} |
👉 6 가지가 끝. 단순함이 강점.
{
"num": 3,
"title": "첫 글입니다",
"content": "안녕하세요",
"writer": "hong",
"viewCount": 0,
"createdAt": "2024-08-15T10:30:00",
"tags": ["spring", "java"],
"replies": [
{"num": 1, "content": "환영합니다"},
{"num": 2, "content": "잘 보고 갑니다"}
]
}
👉 객체 안에 배열·중첩 객체 — 자유롭게 조합 가능.
| 자바 | JSON |
|---|---|
| String | "문자열" |
| int / long / double | 숫자 |
| boolean | true / false |
| null | null |
| List · 배열 | [ ... ] |
| Map · 객체 | { "key": value } |
| LocalDateTime | "2024-08-15T10:30:00" (ISO 8601) |
| LocalDate | "2024-08-15" |
Jackson = 자바 진영의 표준 JSON 라이브러리. Spring MVC 의 기본 의존성에 자동 포함.
public class Board {
private LocalDateTime createdAt;
}
// JSON 출력:
{"createdAt": "2024-08-15T10:30:00"}
// 한국어 형식 원하면 — @JsonFormat
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
private LocalDateTime createdAt;
// → "2024-08-15 10:30"
java.util.Date 는 타임존 처리가 까다로움. 가능하면 LocalDateTime · Instant 사용.
public class Member {
private String id;
private String nick;
@JsonIgnore // ⭐ 응답에 포함 안 함
private String pwd;
}
// 응답 JSON:
{"id": "hong", "nick": "홍길동"}
// pwd 없음
👉 비밀번호 같은 민감 정보가 실수로 응답에 포함되는 것을 방지.
public class Board {
private List<Reply> replies; // 게시글이 댓글들을
}
public class Reply {
private Board board; // 댓글이 게시글을
}
Jackson 이 변환 시 — Board → replies → Board → replies → ... 무한 루프 → StackOverflow.
해결: @JsonManagedReference + @JsonBackReference, 또는 응답 전용 DTO 분리.
// 도메인 객체 (DB 매핑)
public class Board { ... }
// 응답 전용 DTO (REST API 응답)
public class BoardResponse {
private int num;
private String title;
private String writer;
// pwd 없음, 민감 필드 없음
public static BoardResponse from(Board b) {
BoardResponse r = new BoardResponse();
r.num = b.getNum();
r.title = b.getTitle();
r.writer = b.getWriter();
return r;
}
}
👉 응답 전용 클래스를 따로 두면 노출 필드를 명시적으로 통제할 수 있습니다.
// Controller
@GetMapping
public List<BoardResponse> list() {
return service.selectList().stream()
.map(BoardResponse::from)
.toList();
}
👉 도메인 객체를 그대로 노출하지 않고, 응답 DTO 로 한번 변환해서 내보냅니다.
// 받기 — JSON → 객체
const res = await fetch('/api/boards/3');
const board = await res.json();
console.log(board.title); // myboard.num=3 의 제목
// 보내기 — 객체 → JSON
await fetch('/api/boards', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
title: '새 글',
content: '안녕하세요'
})
});
| 증상 | 원인 |
|---|---|
| "Cannot construct instance" | VO 에 기본 생성자 없음 |
| 필드값이 null | setter 없음 또는 이름 불일치 |
| StackOverflowError | 순환 참조 |
| 날짜 형식 이상 | @JsonFormat 미설정 |
| 비밀번호 응답에 포함 | @JsonIgnore 누락 |
JSON 이 「데이터 표기법」 정도라는 것만 안다.
JSON ↔ 자바 매핑 규칙을 알고 흔한 함정을 안다.