JSON
직렬화는 자바 객체를 JSON 형식으로 변환하는 과정이다. 반대로 역직렬화는 JSON 데이터를 자바 객체로 변환하는 과정이다. 이 두 과정은 데이터 교환 시 매우 중요하다.
간단히 말해, JSON.stringify()가 직렬화이고 JSON.parse()가 역직렬화입니다.
더 쉽게 이해할 수 있도록 비유를 들어보겠습니다.
[비유] 레고 조립품을 다른 도시로 보내기
원본 데이터: 멋지게 조립된 레고 성(城)* (메모리 위의 자바스크립트 객체) 직렬화 (Serialization): 이 레고 성을 통째로 보낼 수는 없습니다. 그래서 부품별로 분해해서, 상자에 차곡차곡 담고, 조립 설명서를 넣는 과정입니다. 이제 상자는 운반하기 좋은 상태가 되었습니다. (객체를 전송하기 좋은 JSON 문자열로 바꾸는 과정) 역직렬화 (Deserialization): 다른 도시에 도착한 상자를 열고, 조립 설명서를 보면서 다시 원래의 레고 성으로 조립하는 과정입니다. (JSON 문자열을 받아서 다시 자바스크립트 객체로 만드는 과정)
직렬화 (Serialization)
개념: 메모리 위에서 사용되던 객체(Object)를 디스크에 저장하거나 네트워크를 통해 다른 곳으로 전송할 수 있는 연속적인 데이터 흐름(Stream)이나 문자열 형태로 바꾸는 과정입니다.
목적:
데이터 전송: 다른 시스템(예: 서버-클라이언트)으로 객체를 보내기 위해
데이터 저장: 객체를 파일이나 데이터베이스에 영구적으로 저장하기 위해
자바스크립트에서의 예시: JSON.stringify()
1 // 메모리 위에 존재하는 자바스크립트 객체
2 const userObject = {
3 id: 1,
4 name: "Gildong Hong",
5 email: "hong@example.com",
6 isActive: true
7 };
8
9 // [직렬화] 객체를 -> JSON 문자열로 변환
10 const serializedData = JSON.stringify(userObject);
11
12 console.log(serializedData);
13 // 출력 결과 (전송/저장하기 좋은 문자열 형태):
14 // "{"id":1,"name":"Gildong Hong","email":"hong@example.com","isActive":true}"
역직렬화 (Deserialization)
개념: 직렬화된 데이터(문자열이나 스트림)를 읽어서, 원래의 객체(Object) 구조로 메모리 위에 다시 복원하는 과정입니다.
목적:
전송받은 데이터를 실제 프로그램에서 사용할 수 있는 객체로 만들기 위해
파일이나 데이터베이스에서 읽은 데이터를 객체로 변환하여 사용하기 위해
자바스크립트에서의 예시: JSON.parse()
1 // 서버로부터 전송받은 JSON 문자열 데이터
2 const serializedData = '{"id":1,"name":"Gildong Hong","email":"hong@example.com","isActive":true}';
3
4 // [역직렬화] JSON 문자열을 -> 자바스크립트 객체로 복원
5 const userObject = JSON.parse(serializedData);
6
7 // 이제 자바스크립트에서 사용할 수 있는 객체가 되었다.
8 console.log(userObject.name); // "Gildong Hong"
9 console.log(userObject.email); // "hong@example.com"
@Getter
@NoArgsConstructor
public class OrderCreateRequest {
private List<String> productNumbers;
@Builder
private OrderCreateRequest(List<String> productNumbers) {
this.productNumbers = productNumbers;
}
}
원래 기본 생성자 없으면 JSON 매핑에서 오류 나야 하는데 왜 @NoArgsConstructor 제거해도
동작하지? 스프링 버전 차이?
OjbectMapper는 Jackson 라이브러리에서 제공하는 클래스
주요 기능 :
Java 객체 <-> JSON 형식으로 직렬화/역직렬화
직렬화
Java 객체 → JSON 문자열
writeValueAsString(obj)
역직렬화
JSON 문자열 → Java 객체
readValue(json, Myclass.class)
ObjectMapper objectMapper = new ObjectMapper();
// Java 객체 -> JSON 문자열
User user = new User("Alice", 20);
String json = objectMapper.writeValueAsString(user);
// JSON 문자열 -> Java 객체
String inputJson = "{\"name\":\"Bob\",\"age\":30}";
User user = objectMapper.readValue(inputJson, User.class);
역직렬화 할때 ObjectMapper가 기본 생성자 사용 request 클래스에 NoArgsConstructor 필요
->
User user = objectMapper.readValue(inputJson, User.class);
이 코드는 Jackson의 ObjectMapper
가 JSON 문자열을 User
클래스 객체로 역직렬화(deserialization) 하는 대표적인 예입니다. 내부 구현 및 동작 원리를 아래와 같이 설명할게요:
✅ 1. 내부 동작 요약 (readValue
)
readValue
)// 내부적으로는 이런 구조를 따릅니다:
T readValue(String content, Class<T> valueType) {
JsonParser parser = _jsonFactory.createParser(content);
ObjectReader reader = _config.constructReader(valueType);
return reader.readValue(parser);
}
Jackson의 처리 흐름:
JSON 파싱:
JsonParser
가 문자열inputJson
을 JSON 토큰으로 파싱바인딩:
ObjectReader
가 토큰들을 보고User
클래스의 필드에 값을 채움생성자 호출 또는 필드 직접 접근:
기본 생성자가 있으면 → 생성자 호출 후
setter
또는 리플렉션으로 필드 세팅@JsonCreator
,@JsonProperty
가 붙은 생성자 있으면 → 해당 생성자 사용
✅ 2. 필수 조건: 역직렬화가 잘 되려면
Jackson이 User.class
를 인식하기 위해 필요한 조건:
기본 생성자
있어야 Jackson이 인스턴스를 만들 수 있음 (없으면 @JsonCreator
필요)
필드 or setter
Jackson은 setter
또는 필드에 직접 접근해서 값 설정
@JsonProperty
(optional)
생성자 또는 필드명이 JSON 키와 다르면 명시 필요
예시:
public class User {
private String name;
public User() {} // 기본 생성자
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
✅ 3. @RequestBody
와 관계
@RequestBody
와 관계@PostMapping("/api/user")
public ResponseEntity<Void> createUser(@RequestBody User user)
여기서 @RequestBody
는 Spring MVC가 HTTP 요청 본문을 ObjectMapper
로 자동 역직렬화 하게 합니다.
즉, 위의 objectMapper.readValue(...)
는 내부적으로 Spring도 HttpMessageConverter
를 통해 동일하게 동작합니다.
✅ 요약 정리
ObjectMapper.readValue(...)
JSON → Java 객체로 역직렬화
구현 방식
JsonParser
로 파싱 → ObjectReader
가 필드 매핑
필요 조건
기본 생성자, 필드/setter, 필요시 @JsonProperty
Spring MVC 연동
@RequestBody
가 내부적으로 Jackson 사용함 (readValue
와 동일한 흐름)
@RequestMapping
관계
경로 매핑용이며 역직렬화와 직접적 관계는 없음
실제 테스트
정확히 이해하셨습니다. 아래처럼 정리해드릴게요:
✅ @RequestBody
의 역할
@RequestBody
의 역할@PostMapping("/api/v1/products/new")
public ApiResponse<ProductResponse> createProduct(@Valid @RequestBody ProductCreateRequest request)
✔ @RequestBody
가 하는 일:
@RequestBody
가 하는 일:HTTP 요청의 JSON body를
ProductCreateRequest
객체로 역직렬화(deserialize) 합니다.이걸 생략하면 Spring MVC는
request
파라미터를 폼 필드나 쿼리스트링 등에서 찾으려 함 → JSON body는 무시됨 → 결국request
객체는null
.
👉 따라서 JSON 요청을 처리하려면 @RequestBody
가 반드시 필요합니다.
✅ 응답에서 getter
가 없으면 직렬화 실패
getter
가 없으면 직렬화 실패return ApiResponse.of(HttpStatus.OK, productService.createProduct(request));
여기서 반환된 ProductResponse
객체를 Spring이 JSON으로 직렬화해야 하는데, 만약 ProductResponse
에 getter
가 없으면:
Jackson이 필드 값을 꺼내지 못함
그래서 JSON 응답 생성에 실패
HttpMediaTypeNotAcceptableException
(406) 발생
-> ApiResponse , ProductResponse 둘다 Getter 필요!
🔄 결론적으로:
@RequestBody
request
= null
요청 body에서 값을 못 읽음
getter
406 Not Acceptable
응답 직렬화 실패 (JSON으로 못 만듦)
💡 요약
@RequestBody
는 JSON → Java로 역직렬화할 때 필요getter
는 Java → JSON 직렬화할 때 필요테스트에서
@RequestBody
를 안 달면request
자체가 null이라, 응답 객체까지 안 가므로 직렬화 에러는 안 터짐
완벽한 흐름 이해입니다.
Last updated