데이터 직렬화(Data Serialization)
메모리의 데이터를 물리적으로 저장하거나 전송할 수 있는 형태로 변환하는 작업 (e.g. Bytes, XML, JSON, YAML...)
직렬화가 필요한 이유
데이터의 메모리 구조
- 값 형식 데이터(Value Type): Stack에 '값 그 자체'를 저장해서 바이트 형식으로 변환 가능
- 참조 형식 데이터(Reference Type): Stack에 '참조하는 데이터의 Heap 메모리 주소'를 저장하므로 이 값을 파일 저장하거나 네트워크 전송한들 읽어들이는 시점(프로그램 종료 이후, 외부 메모리 환경)에서는 의미 없는 데이터일 것이다. 따라서 물리적 저장/전송 이전에 직렬화를 통해 참조 시점에 존재하는 데이터를 값 형식으로 변환할 필요가 있다.
직렬화 데이터 형식
다양한 애플리케이션, 플랫폼 또는 시스템 간에 데이터를 교환할 수 있도록 정한 표준화 방식
- Binary: 컴퓨터가 쉽게 이해할 수 있는 이진 방식 (e.g. Bytes...)
- Text: 사람이 쉽게 이해할 수 있는 문자열 방식 (e.g. XML, JSON, YAML...)
XML ( Extensible Markup Language )
- 데이터 정의 규칙을 제공하는 마크업 언어
- Tag를 사용한 트리 구조로 데이터를 표현하여 HTML과 유사한 형태
- 방대한 데이터 저장에 유리
- 스키마 파일로 유효성 검증 가능
- 주석 지원 O
JSON ( JavaScript Object Notation )
- JavaScript 객체를 정의하는 표현식
- 키-값 페어를 사용한 Map과 유사한 형태
- 간단한 구조로 파싱 용이
- 표준 JavaScript 함수로 구문 분석 가능 (JSON.stringify(), JSON.parse())
- 분산 소프트웨어 통신, 웹 애플리케이션, 구성 파일 및 API 등 광범위하게 사용
- 문법 오류에 취약
- 주석 지원 X
YAML
- 키-값 페어를 사용하고 들여쓰기(indent)로 계층을 표현
- XML을 단순화하기 위해 고안되어 가독성이 좋음
- 편의성을 우선하여 자동화, DevOps, 코드형 인프라(IaC) 도구 및 서비스의 구성 파일에 사용
- 주석 지원 O
데이터 역직렬화(Data Deserialization)
디스크나 네트워크로부터 바이트나 문자열을 읽어 메모리에서 사용할 수 있는 형태로 변환하는 작업

자바 객체 직렬화/역직렬화
Serializable 인터페이스, 객체 입출력 스트림
public class Member implements Serializable {
private static final long serialVersionUID = 100L;
private String username;
private String email;
private transient String password;
public Member(String username, String email, String password) {
this.username = username;
this.email = email;
this.password = password;
}
public String hello() {
return "Hello " + username;
}
@Override
public String toString() {
return String.format("Member{name=%s, email=%s, password=%s}", username, email, password);
}
}
- implements java.io.Serializable: 직렬화/역직렬화 대상 클래스가 구현하는 Marker Interface
- serialVersionUID
- Serializable 구현 클래스에서 관리하는 버전 식별자
- 직렬화 데이터에 대상 객체의 버전을 포함하여 역직렬화 시 버전 체크에 사용
- 명시적으로 정의하지 않으면 시스템 내부적으로 class의 기본 해시값을 사용하여 유니크한 번호를 생성하여 관리
- 직렬화하고 싶지 않은 필드는 transient 키워드 사용
public static void main(String[] args) throws IOException, ClassNotFoundException {
Member member = new Member("member", "member@sample.com", "password");
// 직렬화
byte[] serializedMember;
try(ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
try(ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(member);
serializedMember = baos.toByteArray();
}
}
String encodedMember = Base64.getEncoder().encodeToString(serializedMember);
System.out.println(encodedMember);
// 역직렬화
Member deserializedMember;
try (ByteArrayInputStream bais = new ByteArrayInputStream(serializedMember)) {
try (ObjectInputStream ois = new ObjectInputStream(bais)) {
Object objectMember = ois.readObject();
deserializedMember = (Member) objectMember;
}
}
System.out.println(deserializedMember);
System.out.println(deserializedMember.hello());
}
- java.io.ObjectOutputStream: 메모리 상의 자바 객체를 직렬화하는 메서드( writeObject() ) 제공
- java.io.ObjectInputStream: 직렬화된 바이트 데이터를 객체로 변환하는 역직렬화 메서드( readObject() ) 제공
실행 결과
rO0ABXNyACVjb20uZXhhbXBsZS5qYXZhcHJvamVjdC5kb21haW4uTWVtYmVyv5xtVCfJQ9cCAAJMAAVlbWFpbHQAEkxqYXZhL2xhbmcvU3RyaW5nO0wACHVzZXJuYW1lcQB+AAF4cHQAEW1lbWJlckBzYW1wbGUuY29tdAAGbWVtYmVy
Member{name=member, email=member@sample.com, password=null}
Hello member
Jackson ObjectMapper
Jackson 라이브러리 추가
<!-- pom.xml 의존성 추가 (maven 방식) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>버전</version>
</dependency>
JSON 직렬화/역직렬화
public static void main(String[] args) throws IOException, ClassNotFoundException {
Member member = new Member("member", "member@sample.com", "password");
// 직렬화: Object to JSON
ObjectMapper objectMapper = new ObjectMapper();
String serializedMember = objectMapper.writeValueAsString(member);
System.out.println(serializedMember);
// 역직렬화: JSON to Object
Member deserializedMember = objectMapper.readValue(serializedMember, Member.class);
System.out.println(deserializedMember);
System.out.println(deserializedMember.hello());
}
}
- ObjectMapper.writeValueAsString() : 객체를 JSON 문자열로 변환
- ObjectMapper.readValue() : 직렬화된 데이터를 객체로 변환 (String, byte[], URL, File, InputStream, Reader... to Object)
- 직렬화 과정에서 접근지시자에 의해 하나의 필드도 접근할 수 없으면 InvalidDefinitionException 발생
- private 필드의 경우 getter 필요
- 역직렬화 과정에서 Reflection을 통해 객체를 생성하므로 대상 클래스에 기본 생성자(default constructor)가 없으면 InvalidDefinitionException 발생
실행 결과
{"username":"member","email":"member@sample.com","password":"password"}
domain.Member{name=member, email=member@sample.com, password=password}
Hello member
※ 특정 필드 직렬화/역직렬화 배제하는 방법
- @JsonIgnoreProperties: 클래스 레벨로 특정 필드 지정
- @JsonIgnore: 필드 레벨로 지정
- @JsonIgnoreType: 클래스 레벨로 전체 클래스 지정