HooneyLog
© 2026 Seunghoon Shin. All rights reserved.
모든 게시글
Backend
2026. 3. 29.•
1

[TS/JS] 직렬화(Serialization)와 역직렬화(Deserialization) 깊게 파보기

Seunghoon Shin
작성자 Seunghoon Shin풀스택 개발자

[TS/JS] 직렬화(Serialization)와 역직렬화(Deserialization) 깊게 파보기

1. 문제의 배경

프론트엔드와 백엔드가 통신하거나, 메모리에 있는 데이터를 데이터베이스나 캐시(Redis)에 저장할 때 우리는 항상 한 가지 물리적인 장벽에 부딪힙니다. 바로 "메모리에 존재하는 객체(Object)를 어떻게 네트워크나 디스크를 통해 전달할 것인가?" 입니다.

자바스크립트 메모리에 존재하는 객체는 참조(Reference)와 메서드, 프로토타입 체인 등 복잡한 구조로 얽혀 있습니다. 이 입체적인 데이터를 랜선(네트워크)을 통해 전송하거나 하드디스크에 기록하려면 1차원의 바이트(Byte) 스트림이나 문자열로 평면화하는 작업이 필요합니다.

초보 시절 흔히 겪는 [object Object] 문자열 출력 이슈나, 백엔드에서 받은 날짜 데이터가 Date 객체가 아닌 string으로 잡혀 getFullYear() 메서드를 사용할 수 없게 되는 에러가 모두 이 과정에 대한 이해가 부족할 때 발생합니다.

2. 해결 방안 탐색

이 문제를 해결하기 위한 일련의 과정을 우리는 직렬화(Serialization) 와 역직렬화(Deserialization) 라고 부릅니다.

  • 직렬화 (Serialization): 메모리의 객체 상태를 저장하거나 전송할 수 있는 포맷(예: JSON, XML, 바이너리 등)으로 변환하는 과정입니다. JS 생태계에서는 주로 JSON.stringify()를 사용합니다.
  • 역직렬화 (Deserialization): 전송받은 데이터(포맷)를 다시 메모리에서 다룰 수 있는 객체로 복원하는 과정입니다. 주로 JSON.parse()를 사용합니다.

대부분의 웹 개발에서는 JSON(JavaScript Object Notation)이 표준 데이터 교환 포맷으로 자리 잡았습니다. 하지만 단순한 JSON 포맷은 함수(메서드), undefined, Symbol, 순환 참조(Circular Reference), 그리고 Date나 Map, Set 같은 특수 객체를 온전히 표현하지 못한다는 한계가 있습니다.

3. 핵심 개념 및 아키텍처

데이터가 시스템 경계를 넘어갈 때 어떤 일이 발생하는지 시각적으로 이해해 봅시다.

클라이언트가 보낸 데이터가 서버에 도착했을 때, JSON.parse()를 거친 직후의 데이터는 그저 순수한 데이터 덩어리(Plain Old JavaScript Object, POJO)일 뿐입니다. 만약 서버에서 이 데이터가 특정 클래스의 인스턴스이길 기대한다면(메서드를 호출하기 위해), 단순 파싱을 넘어 인스턴스화(Instantiation) 과정이 추가로 필요합니다.

4. 구현 및 트러블슈팅

4.1. NestJS와 class-transformer

NestJS에서는 들어오는 JSON 데이터를 우리가 정의한 DTO(Data Transfer Object) 클래스의 인스턴스로 변환하기 위해 class-transformer를 사용합니다.

import { Type } from 'class-transformer'; import { IsDate, IsString } from 'class-validator'; export class UserDto { @IsString() name: string; // JSON으로 받은 날짜 문자열("2023-01-01T00:00:00.000Z")을 // 실제 JavaScript Date 객체로 역직렬화합니다. @Type(() => Date) @IsDate() birthDate: Date; get age() { return new Date().getFullYear() - this.birthDate.getFullYear(); } }

ValidationPipe에 transform: true 옵션을 주면, NestJS는 내부적으로 평범한 JSON 객체를 위 UserDto의 인스턴스로 변환(역직렬화 및 인스턴스화)해 줍니다. 덕분에 컨트롤러에서 안전하게 userDto.age 같은 getter나 메서드를 호출할 수 있습니다.

4.2. Next.js App Router (RSC)에서의 직렬화

React Server Components(RSC)가 도입되면서, 서버 컴포넌트에서 클라이언트 컴포넌트로 props를 넘길 때도 직렬화가 발생합니다. 초기 Next.js에서는 Date 객체를 클라이언트로 넘기면 Error: Only plain objects can be passed to Client Components from Server Components... 에러가 발생했습니다. (JSON.stringify의 한계)

하지만 최근 React와 Next.js는 자체적인 직렬화 포맷(RSC Payload)을 사용하여 Date, Map, Set, 심지어 Promise까지 직렬화/역직렬화하여 넘길 수 있도록 발전했습니다.

// Server Component import ClientComponent from './ClientComponent'; export default async function Page() { const data = await fetchDatabase(); const currentDate = new Date(); // 이제 Date 객체도 그대로 전달 가능! return <ClientComponent data={data} time={currentDate} /> }

4.3. 트러블슈팅: 순환 참조 (Circular Reference)

데이터베이스 엔티티를 다룰 때(예: TypeORM이나 Prisma) 양방향 관계가 설정되어 있으면 직렬화 시 무한 루프에 빠집니다.

const user = { name: 'Hooney' }; const post = { title: 'First Post', author: user }; user.posts = [post]; // 순환 참조 발생! JSON.stringify(user); // TypeError: Converting circular structure to JSON

해결책:

  1. 전송할 데이터 구조(DTO)를 명확히 분리하여 순환 참조의 고리를 끊습니다.
  2. class-transformer의 @Exclude()나 패스워드 숨김 처리 등의 방식을 활용하여 응답 객체에서 불필요한 참조를 제거합니다.

5. 결과 및 Trade-off

고려해야 할 Trade-off:

  • 성능 오버헤드: JSON의 직렬화/역직렬화는 CPU 집약적인 작업입니다. 대용량 배열이나 복잡한 객체를 다룰 때 JSON.parse는 메인 스레드를 블로킹(Blocking)할 수 있습니다.
  • 포맷의 한계와 대안: JSON은 텍스트 기반이라 사람이 읽기 좋지만, 데이터 크기가 크고 파싱이 느립니다. 초고성능 마이크로서비스 간 통신이나 대용량 데이터 전송이 필요한 경우, JSON을 버리고 gRPC (Protocol Buffers), MessagePack, Avro 같은 바이너리 직렬화 포맷을 도입하는 것을 고려해야 합니다. 이들은 스키마를 미리 정의하여 직렬화 속도와 크기를 극적으로 최적화합니다.

6. 마치며

직렬화와 역직렬화는 프론트엔드, 백엔드, 데이터베이스라는 서로 다른 세계를 이어주는 다리이자 언어입니다. 데이터가 시스템의 경계를 넘나들 때 어떤 형태로 변환되고 복원되는지 명확히 이해한다면, 예기치 않은 데이터 유실이나 타입 에러를 사전에 방지하고 더 견고한 애플리케이션을 설계할 수 있을 것입니다.

참고 자료:

  • MDN Web Docs - JSON
  • class-transformer 공식 문서
  • React Server Components Payload 분석
← 이전 글[NestJS] Pipe 완벽 가이드: 데이터 검증과 변환의 예술
다음 글 →[NestJS] 요청 라이프사이클 마스터 가이드: 미들웨어부터 인터셉터까지