배경

프로젝트에 Sequelize 객체를 JSON stringify 하여 캐시 키로 사용하는 부분이 있는데, 심볼이 무시되는 바람에 모두 같은 값으로 캐싱되는 이슈가 있었다. 왜 심볼은 stringify 에서 무시되는지, 그리고 Sequelize는 왜 심볼을 사용하는지를 정리해보았다.

 

왜 심볼은 stringify 에서 무시되는가

ECMAScript 명세에 따라 Symbol을 직렬화하지 않도록 명시되어있음.

  1. JSON 자체가 Text 기반 포맷 → Symbol은 런타임 고유 식별자라 문자열로 안전하게 표현할 방법이 없음.
  2. 상호 운용성 문제 → JSON은 다양한 언어에서 읽어야 하는데 대부분의 언어에는 Symbol 개념이 없음.
  3. 보안적/추상적 의미 → Symbol은 객체의 "숨겨진" 속성을 위해 설계됨. JSON 직렬화는 보통 데이터 교환 용도라 굳이 숨겨진 속성을 포함하지 않음.

 

왜 Sequelize는 심볼을 사용하는가

 

Sequelize는 동적 언어(자바스크립트)의 특성과 Active Record 철학을 살리기 위해 런타임 파싱 방식을 선택했다.

prisma 와 달리 sequelize.js 는 dataValues, _options, _previousDataValues 와 같은 런타임 상태를 객체에 직접 포함한다.

즉 런타임 파싱 시점에 사용자 정의 키와의 충돌을 막을 수 있고, 객체 중 내부 정보를 은닉하기 위함일 것이다.

 

 

JS 개발자는 어떻게 대응할 수 있는가

  1. replacer 함수 활용 - JSON.stringify의 두 번째 인자인 replacer를 사용해서 Symbol을 커스텀 직렬화할 수 있음
const obj = {
    [Symbol('id')]: 123,
    name: 'Alice',
    role: Symbol('admin'),
};

const json = JSON.stringify(obj, (key, value) => {
    if (typeof value === 'symbol') {
        return value.toString(); // 또는 Symbol.keyFor(value) 등 활용
    }
    return value;
});

console.log(json);
// {"name":"Alice","role":"Symbol(admin)"}

 

    2. flatted, circular-json, json-stringify-safe 등 심볼 직렬화까지 확장하거나 커스텀 핸들링하는 라이브러리 활용

+ Recent posts