이번 글은 단순히 JSON 변환을 설명하는 글이 아니다.
실무에서는 매일 사용하는 기능이지만 내부 원리를 이해하는 개발자는 생각보다 많지 않다.
Spring에서
@PostMapping
public User save(
@RequestBody UserRequest request
) {
...
}
이 코드가 왜 가능한지 이해하려면 반드시 알아야 하는 개념이다.
Serialization / Deserialization
객체(Object)와 저장 또는 전송 가능한 형태(JSON, 파일, 바이트 등) 사이를 변환하는 과정
먼저 왜 필요한가?
메모리에 있는 객체는 JVM만 이해할 수 있다.
예를 들어
User user = new User();
user.setName("Kim");
user.setAge(30);
우리가 보기에는
name = Kim
age = 30
처럼 보인다.
하지만 실제 메모리에는
Heap
--------------------
User 객체
0x1000
--------------------
name → 0x3000
age → 30
--------------------
String 객체
0x3000
"KIM"
처럼 주소(reference)와 객체로 저장되어 있다.
이 상태 그대로는
네트워크 전송
파일 저장
DB 저장
Redis 저장
을 할 수 없다.
다른 JVM은
0x1000
0x3000
이라는 주소를 절대로 이해하지 못하기 때문이다.
그래서 필요한 것이 Serialization이다.
객체를
객체
↓
문자열(JSON)
↓
바이트(byte[])
↓
파일 또는 네트워크
형태로 변환하는 과정이다.
Serialization
예를 들어
public class User {
private String name;
private int age;
}
객체
User user = new User();
user.setName("Kim");
user.setAge(30);
Serialization 결과(JSON)
{
"name": "Kim",
"age": 30
}
그림으로 보면
Heap
User 객체
↓
Serialization
↓
"{\"name\":\"Kim\",\"age\":30}"
↓
파일
네트워크
Redis
Kafka
Deserialization
반대 과정이다.
JSON
{
"name": "Kim",
"age": 30
}
↓
Java 객체
User user
user.getName()
↓
Kim
즉
JSON
↓
Deserialization
↓
Java Object
이다.
Spring에서는 언제 사용할까?
가장 대표적인 예가
@PostMapping
public UserResponse save(
@RequestBody UserRequest request
) {
}
이다.
클라이언트가
{
"name": "Kim",
"age": 30
}
를 보낸다.
Spring 내부에서는
HTTP Body
↓
JSON
↓
Jackson
↓
Deserialization
↓
UserRequest 객체 생성
↓
Controller 전달
을 수행한다.
개발자는
request.getName()
만 사용하면 된다.
응답도 마찬가지이다.
Controller
return new UserResponse("OK");
Spring 내부
UserResponse 객체
↓
Jackson
↓
Serialization
↓
JSON
↓
HTTP Response
클라이언트가 받는 것은
{
"message": "OK"
}
이다.
Jackson은 어떻게 객체를 만들까?
Reflection을 떠올려 보자.
우리는 이미 공부했다.
Reflection은
Class 객체
↓
생성자 찾기
↓
newInstance()
↓
Field 접근
↓
setter 호출
이 가능하다.
Jackson도 거의 동일하다.
JSON
{
"name":"Kim",
"age":30
}
↓
Reflection
User.class
↓
객체 생성
↓
setName("Kim")
↓
setAge(30)
↓
완성
User 객체
그래서 기본 생성자가 중요하다.
예를 들어
public class User {
private String name;
public User(String name) {
this.name = name;
}
}
만 존재한다고 가정하자.
Jackson은
Reflection
↓
newInstance()
↓
기본 생성자 없음
↓
실패
가 된다.
그래서 예전에는
실무에서 거의 항상
public class User {
public User() {
}
}
를 만들어 두었다.
Lombok을 많이 사용하는 이유
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
이런 Annotation을 자주 사용하는 이유도
Serialization / Deserialization과 관련이 있다.
Serializable 인터페이스는 무엇인가?
많은 사람들이
JSON 변환
=
Serializable
라고 생각한다.
하지만 다르다.
Serializable은
public class User
implements Serializable {
}
처럼 사용한다.
이것은
Java 객체를 Java 바이트 스트림으로 직렬화하기 위한 마커 인터페이스이다.
대표적인 사용처
ObjectOutputStream
↓
파일 저장
↓
ObjectInputStream
↓
복원
예제
ObjectOutputStream out =
new ObjectOutputStream(...);
out.writeObject(user);
복원
ObjectInputStream in =
new ObjectInputStream(...);
User user =
(User) in.readObject();
JSON과 Serializable 차이
| 사람이 읽을 수 있다 | 사람이 읽기 어렵다 |
| 언어 독립적 | Java 전용 |
| HTTP API에서 사용 | Java 객체 저장 |
| Reflection 기반 | JVM 직렬화 메커니즘 |
transient
Serializable에는 중요한 키워드가 있다.
public class User
implements Serializable {
private String name;
private transient String password;
}
Serialization 하면
name
↓
저장
password
↓
무시
된다.
즉
민감한 정보는 저장하지 않을 수 있다.
serialVersionUID
private static final long serialVersionUID = 1L;
도 많이 본다.
왜 필요할까?
예를 들어
오늘
class User {
String name;
}
으로 저장했다.
내일
class User {
String name;
int age;
}
로 변경했다.
이전 파일을 읽으면
객체 구조가 달라져 문제가 생길 수 있다.
serialVersionUID는
객체 버전 번호
역할을 한다.
Spring에서는 Serializable을 많이 사용할까?
예전에는
HttpSession
RMI
EJB
Java 객체 저장
등에서 많이 사용했다.
하지만 최근 Spring Boot에서는
대부분
JSON
Redis(JSON)
Kafka(JSON)
REST API(JSON)
를 사용하기 때문에
Serializable 사용 빈도는 많이 줄었다.
실무에서 가장 많이 사용하는 Serialization
Controller
↓
@RequestBody
↓
Jackson Deserialization
↓
Java Object
↓
Service
↓
Response Object
↓
Jackson Serialization
↓
JSON Response
이 과정이 하루에도 수천 번 수행된다.
면접 포인트
Serialization이란?
메모리에 있는 객체를 저장하거나 전송 가능한 형태(JSON, byte[], 파일 등)로 변환하는 과정이다.
Deserialization이란?
저장 또는 전송된 데이터를 다시 Java 객체로 복원하는 과정이다.
Spring MVC에서 어떻게 동작하는가?
HTTP Body
↓
Jackson
↓
Reflection
↓
객체 생성
↓
Controller 전달
Serializable과 JSON Serialization의 차이
- Serializable은 Java 객체를 Java 바이트 스트림으로 직렬화하는 방식이다.
- Jackson JSON Serialization은 객체를 JSON 문자열로 변환하는 방식이며 REST API에서 가장 많이 사용된다.
다음 글 추천
이 흐름에서는 AutoBoxing / Unboxing이 가장 자연스럽다.
겉보기에는 단순한 문법 설탕(Syntactic Sugar)이지만,
- Integer Cache
- == 비교 함정
- NPE가 발생하는 이유
- 컬렉션이 primitive를 담을 수 없는 이유
까지 모두 연결되어 이해할 수 있고, 이후 Integer Cache 글로 자연스럽게 이어질 수 있다.
'language > java' 카테고리의 다른 글
| Integer Cache (0) | 2026.06.16 |
|---|---|
| AutoBoxing / Unboxing (0) | 2026.06.16 |
| JDK Proxy vs CGLIB (0) | 2026.06.16 |
| Dynamic Proxy 원리 (0) | 2026.06.16 |
| Meta Annotation (0) | 2026.06.16 |
댓글