JSON은 자바스크립트 객체 표기법에서 유래했지만, 이제는 언어에 독립적인 데이터 교환 포맷입니다. JSON.parseJSON.stringify는 단순해 보이지만, replacerreviver 같은 고급 기능을 알면 훨씬 유연하게 활용할 수 있습니다.

JSON.stringify 기본

자바스크립트 값을 JSON 문자열로 변환합니다.

JS
const user = { name: "정훈", age: 25, isAdmin: false };

JSON.stringify(user);
// '{"name":"정훈","age":25,"isAdmin":false}'

// 들여쓰기 추가 (디버깅용)
JSON.stringify(user, null, 2);
// {
//   "name": "정훈",
//   "age": 25,
//   "isAdmin": false
// }

stringify가 무시하는 값들

JS
const obj = {
  name: "정훈",
  fn: function () {},     // 함수 → 무시
  sym: Symbol("id"),      // Symbol → 무시
  undef: undefined,       // undefined → 무시
  nan: NaN,               // NaN → null
  inf: Infinity,          // Infinity → null
  date: new Date(),       // Date → ISO 문자열
  regex: /abc/,           // RegExp → 빈 객체 {}
  map: new Map([["a", 1]]), // Map → 빈 객체 {}
};

console.log(JSON.stringify(obj, null, 2));
// {
//   "name": "정훈",
//   "nan": null,
//   "inf": null,
//   "date": "2026-03-28T06:30:00.000Z",
//   "regex": {},
//   "map": {}
// }

undefined, 함수, Symbol은 완전히 사라지고, NaNInfinitynull이 됩니다. 면접에서 자주 물어보는 포인트입니다.

replacer — 직렬화 필터

stringify의 두 번째 인자로 직렬화를 제어할 수 있습니다.

배열 replacer — 특정 키만 포함

JS
const user = { name: "정훈", password: "1234", email: "a@b.c" };

// password 제외하고 직렬화
const safe = JSON.stringify(user, ["name", "email"]);
console.log(safe); // '{"name":"정훈","email":"a@b.c"}'

함수 replacer — 값 변환

JS
const data = {
  name: "정훈",
  password: "secret",
  createdAt: new Date("2026-01-01"),
};

const result = JSON.stringify(data, (key, value) => {
  // 비밀번호 마스킹
  if (key === "password") return "****";
  // Date를 한국어 형식으로
  if (value instanceof Date) return value.toLocaleDateString("ko-KR");
  return value;
}, 2);

console.log(result);
// {
//   "name": "정훈",
//   "password": "****",
//   "createdAt": "2026. 1. 1."
// }

JSON.parse 기본

JSON 문자열을 자바스크립트 값으로 변환합니다.

JS
const json = '{"name":"정훈","age":25}';
const obj = JSON.parse(json);
console.log(obj.name); // "정훈"

// 잘못된 JSON은 SyntaxError
try {
  JSON.parse("{ name: '정훈' }"); // 작은따옴표, 키에 따옴표 없음
} catch (e) {
  console.log(e.message); // SyntaxError
}

JSON 규칙

JS
// 유효한 JSON
'{"key": "value"}'   // 키는 반드시 쌍따옴표
'[1, 2, 3]'          // 배열도 유효
'"hello"'            // 문자열도 유효
'42'                 // 숫자도 유효
'true'               // 불린도 유효
'null'               // null도 유효

// 무효한 JSON
"{ key: 'value' }"   // 작은따옴표, 키에 따옴표 없음
'{name: "a",}'       // 후행 쉼표 불가
"undefined"          // undefined는 JSON에 없음

reviver — 역직렬화 변환

parse의 두 번째 인자로 값을 변환할 수 있습니다.

JS
const json = '{"name":"정훈","birthDate":"1999-05-15T00:00:00.000Z"}';

// 날짜 문자열을 Date 객체로 자동 변환
const obj = JSON.parse(json, (key, value) => {
  // ISO 날짜 패턴 감지
  if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
    return new Date(value);
  }
  return value;
});

console.log(obj.birthDate instanceof Date); // true
console.log(obj.birthDate.getFullYear());   // 1999

toJSON — 커스텀 직렬화

객체에 toJSON 메서드가 있으면 stringify가 자동으로 호출합니다.

JS
class User {
  constructor(name, password) {
    this.name = name;
    this.password = password;
  }

  // JSON.stringify가 이 메서드를 호출
  toJSON() {
    return {
      name: this.name,
      // password는 제외
    };
  }
}

const user = new User("정훈", "secret123");
console.log(JSON.stringify(user)); // '{"name":"정훈"}'

Date 객체가 ISO 문자열로 직렬화되는 이유도 Date.prototype.toJSON이 정의되어 있기 때문입니다.

순환 참조 처리

JS
const a = {};
const b = { a };
a.b = b; // 순환 참조

// JSON.stringify(a); // TypeError: circular structure

// 순환 참조 제거 replacer
function getCircularReplacer() {
  const seen = new WeakSet();
  return (key, value) => {
    if (typeof value === "object" && value !== null) {
      if (seen.has(value)) return "[Circular]";
      seen.add(value);
    }
    return value;
  };
}

console.log(JSON.stringify(a, getCircularReplacer(), 2));
// { "b": { "a": "[Circular]" } }

깊은 복사로서의 JSON

JS
// 간단한 깊은 복사 (함정 있음)
const original = { a: 1, nested: { b: 2 } };
const copy = JSON.parse(JSON.stringify(original));

copy.nested.b = 999;
console.log(original.nested.b); // 2 — 독립적

// 이 방식의 한계
const problematic = {
  date: new Date(),     // Date → 문자열이 됨
  fn: () => {},         // 함수 → 사라짐
  undef: undefined,     // undefined → 사라짐
  regex: /test/,        // RegExp → 빈 객체
  map: new Map(),       // Map → 빈 객체
};

// 대안: structuredClone (모던 브라우저)
const safeCopy = structuredClone(problematic);
// Date, Map, Set 등을 올바르게 복사 (함수는 여전히 불가)

실전 패턴

JS
// 1. 로컬 스토리지 활용
const saveData = (key, data) =>
  localStorage.setItem(key, JSON.stringify(data));

const loadData = (key) => {
  const raw = localStorage.getItem(key);
  return raw ? JSON.parse(raw) : null;
};

// 2. 안전한 JSON 파싱
function safeParse(json, fallback = null) {
  try {
    return JSON.parse(json);
  } catch {
    return fallback;
  }
}

// 3. 객체 비교 (간이 방식)
function isEqual(a, b) {
  return JSON.stringify(a) === JSON.stringify(b);
}
// 주의: 키 순서가 다르면 false, 특수 값 처리 안 됨

** 기억하기 **: JSON.stringify는 함수, Symbol, undefined를 무시하고, NaN/Infinity는 null로 바꿉니다. replacer/reviver를 활용하면 직렬화/역직렬화를 세밀하게 제어할 수 있습니다. 깊은 복사 용도로는 structuredClone이 더 안전합니다.

댓글 로딩 중...