본문으로 건너뛰기
김신건의 로그

[Javascript] JSON

· 수정 · 📖 약 1분 · 577자/단어 #javascript #json #serialization
JS JSON, JSON.stringify, JSON.parse

정의

JSON (JavaScript Object Notation) 은 데이터 교환 형식. JS 의 객체 리터럴과 비슷하지만 엄격한 문법.

  • JSON.stringify(value) : JS 값 → JSON 문자열
  • JSON.parse(text) : JSON 문자열 → JS 값

stringify

JSON.stringify({ a: 1, b: [2, 3] })
// '{"a":1,"b":[2,3]}'

JSON.stringify({ a: 1 }, null, 2)
// pretty-print, 2-space indent
// {
//   "a": 1
// }

parse

JSON.parse('{"a":1}')        // { a: 1 }
JSON.parse('[1, 2, 3]')      // [1, 2, 3]
JSON.parse('null')            // null
JSON.parse('true')            // true
JSON.parse('"hello"')         // 'hello'

replacer (stringify)

JSON.stringify({ a: 1, password: 'secret' }, (key, value) => {
    return key === 'password' ? undefined : value;
});
// '{"a":1}'

// 또는 array (allowlist)
JSON.stringify(obj, ['a', 'b']);    // a, b 만 포함

reviver (parse)

JSON.parse(text, (key, value) => {
    if (key === 'date') return new Date(value);
    return value;
});

date / BigInt 등 표준 외 타입 복원에 유용.

누락되는 값들

stringify 가 무시하거나 변환하는 값:

입력결과
undefined (값)키 제외
undefined (배열 원소)null
Symbol (값)키 제외
function키 제외
NaN, Infinitynull
DatetoISOString() 호출 → string
BigInt❌ TypeError
순환 참조❌ TypeError
JSON.stringify({
    a: undefined,
    b: Symbol(),
    c: () => {},
    d: NaN,
    e: new Date(),
    f: 1n,    // ❌
});
// '{"d":null,"e":"2024-..."}'   (a, b, c 제외)

toJSON 메서드

객체에 toJSON 메서드가 있으면 stringify 가 호출.

class Money {
    constructor(amount) { this.amount = amount; }
    toJSON() {
        return `$${this.amount.toFixed(2)}`;
    }
}

JSON.stringify(new Money(99.9));    // '"$99.90"'

Date 가 ISO string 되는 것도 이 메커니즘.

깊은 복사 (제한적)

const copy = JSON.parse(JSON.stringify(obj));

장점: 단순. 단점:

  • Date → string 으로 변환됨
  • function, undefined, Symbol 누락
  • BigInt, 순환 참조 에러

깊은 복사는 structuredClone(obj) 권장 (ES2022+).

자주 쓰는 패턴

API 통신

const response = await fetch('/api', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
});
const json = await response.json();   // 내부적으로 JSON.parse

localStorage

localStorage.setItem('user', JSON.stringify(user));
const user = JSON.parse(localStorage.getItem('user'));

디버깅 출력

console.log(JSON.stringify(complexObj, null, 2));

보안 (민감 정보 제거)

function sanitize(obj) {
    return JSON.parse(JSON.stringify(obj, (k, v) =>
        ['password', 'token', 'apiKey'].includes(k) ? undefined : v
    ));
}

함정

1. 한국어 escape

JSON.stringify({ name: '한글' })
// '{"name":"한글"}'  (UTF-8 그대로)

// 옛 환경 호환을 위해 ASCII 만 원한다면 직접 escape

2. 정수 정밀도

JSON.parse('9007199254740993')      // 9007199254740992 (정밀도 손실)

큰 정수는 JSON 문자열 → BigInt 수동 변환.

3. 순환 참조

const a = {};
a.self = a;
JSON.stringify(a)    // ❌ TypeError

해법: replacer 로 순환 감지 또는 외부 라이브러리.

4. 키 순서

JSON.parse(JSON.stringify(obj))
// 객체 키 순서가 보존되긴 하지만 표준은 보장 안 함

비교/해시에 키 순서를 의존하지 말 것.

JSON5 / JSONC

{
    // 주석 가능
    "a": 1,
    "b": 2,    // trailing comma
}

JSON 의 확장 (주석, 후행 쉼표). 표준이 아니라 라이브러리 필요. tsconfig.json 이 JSONC 사용.

직렬화 흐름 시각화

flowchart LR
    JS["JS 값\n(object, array, ...)"]
    STR["JSON 문자열\n(text)"]
    JS -->|"JSON.stringify(value, replacer, space)"| STR
    STR -->|"JSON.parse(text, reviver)"| JS
flowchart TD
    V["입력 값"]
    V --> HasToJSON{"toJSON 메서드?"}
    HasToJSON -->|"있음"| CallToJSON["toJSON() 호출"]
    HasToJSON -->|"없음"| TypeCheck["타입 확인"]
    CallToJSON --> TypeCheck
    TypeCheck -->|"string / number / boolean / null"| Literal["리터럴로 출력"]
    TypeCheck -->|"Array"| ArrayLoop["각 원소 재귀 직렬화"]
    TypeCheck -->|"Object"| ObjLoop["각 키-값 재귀 직렬화"]
    TypeCheck -->|"undefined / Function / Symbol"| Skip["생략 (키 제외 / null)"]
    TypeCheck -->|"BigInt"| Throw["TypeError"]
    TypeCheck -->|"순환 참조"| Throw2["TypeError"]

structuredClone 과 비교

ES2022 에서 도입된 structuredClone 은 JSON 경유 없는 진짜 깊은 복사.

const obj = {
    date: new Date(),
    fn: () => {},
    x: undefined,
    big: 9007199254740993n,
};

// JSON 방식 - 문제 많음
const via_json = JSON.parse(JSON.stringify(obj));
// { date: '2024-...', x 없음, fn 없음 }  ← Date 가 string, BigInt 는 에러

// structuredClone - 권장
const clone = structuredClone({ date: new Date(), x: [1, 2] });
// { date: Date, x: [1, 2] }   ← 타입 보존
항목JSON 방식structuredClone
Datestring 변환Date 보존
undefined키 제외undefined 보존
Function키 제외지원 안 함
RegExp{} 로 변환RegExp 보존
Map / Set{} / []보존
BigIntTypeError보존
순환 참조TypeError보존
ArrayBufferbase64 표현 필요보존

단순 직렬화 목적 외 깊은 복사는 structuredClone 우선.

에러 처리 패턴

// JSON.parse 는 잘못된 JSON 에서 SyntaxError
function safeJsonParse(str, fallback = null) {
    try {
        return JSON.parse(str);
    } catch {
        return fallback;
    }
}

// optional chaining 과 함께
const user = safeJsonParse(localStorage.getItem('user'), {});
const name = user?.name ?? 'anonymous';

커스텀 직렬화 패턴

Map / Set 직렬화

function replacer(key, value) {
    if (value instanceof Map) {
        return { __type: 'Map', value: [...value.entries()] };
    }
    if (value instanceof Set) {
        return { __type: 'Set', value: [...value] };
    }
    return value;
}

function reviver(key, value) {
    if (value && value.__type === 'Map') {
        return new Map(value.value);
    }
    if (value && value.__type === 'Set') {
        return new Set(value.value);
    }
    return value;
}

const data = { m: new Map([['a', 1]]), s: new Set([1, 2]) };
const json = JSON.stringify(data, replacer);
const restored = JSON.parse(json, reviver);

BigInt 직렬화 (별도 처리)

// BigInt 는 기본 JSON.stringify 에서 TypeError
// replacer 로 처리
const json = JSON.stringify(bigIntValue, (key, value) =>
    typeof value === 'bigint' ? value.toString() + 'n' : value
);

// reviver 로 복원
const restored = JSON.parse(json, (key, value) => {
    if (typeof value === 'string' && /^\d+n$/.test(value)) {
        return BigInt(value.slice(0, -1));
    }
    return value;
});

성능 고려

상황
큰 객체 stringifyreplacer 배열로 필요한 키만 포함
반복 직렬화결과를 캐시하거나 불변 객체 사용
스트림 처리Node.js 의 stream-json 같은 라이브러리
정밀 숫자BigInt 또는 string 처리
// replacer 배열로 화이트리스트 - 불필요한 키 직렬화 생략
const lean = JSON.stringify(bigObj, ['id', 'name', 'email']);

JSON Schema (타입 검증)

JSON 문서의 구조를 검증하는 표준. 직접 JSON 이 아닌 별도 스키마 레이어.

{
    "type": "object",
    "required": ["id", "name"],
    "properties": {
        "id":   { "type": "integer" },
        "name": { "type": "string", "minLength": 1 }
    }
}

JS 에서는 ajv, zod.parse, @sinclair/typebox 등으로 런타임 검증.

import { z } from 'zod';

const UserSchema = z.object({
    id:   z.number().int(),
    name: z.string().min(1),
});

const user = UserSchema.parse(JSON.parse(raw));  // 검증 + 타입 추론

참고

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기