JSON은 객체가 아니라 그냥 텍스트다
자바스크립트 JSON(JavaScript Object Notation)은 데이터를 주고받기 위한 _텍스트 포맷_이다. 생김새는 자바스크립트 객체 리터럴과 똑같아 보이지만, 사실은 문자열이다. 네트워크로 전송하거나 파일에 저장하거나 설정 파일에 붙여넣을 수 있는 문자들의 나열일 뿐이다.
// JavaScript 객체 — 메모리 속에 살아 있는 값.
const user = { name: "Rosa", age: 30 };
// JSON — 같은 데이터를 표현한 텍스트 문자열.
const json = '{"name":"Rosa","age":30}';
이 둘은 생김새가 비슷해서 헷갈리기 쉽습니다. 헷갈리지 않으려면 이렇게 기억해 두면 좋습니다. 객체는 프로그램 안에서 살아 숨쉬는 존재이고, JSON은 그 객체가 바깥으로 나갈 때 입는 옷이다. 이 둘 사이를 오가는 함수가 바로 JSON.stringify(객체 → 문자열)와 JSON.parse(문자열 → 객체)입니다.
JSON.stringify 사용법: 객체를 문자열로 변환하기
자바스크립트 값을 JSON 형식으로 바꾸고 싶을 때는 다음과 같이 씁니다:
결과는 공백이 전혀 없는 한 줄짜리 문자열입니다. 용량도 작고 네트워크로 전송하기에 딱 좋죠. typeof로 확인해 보면 object가 아니라 string이 나옵니다.
디버깅할 때 좀 더 읽기 편하게 출력하고 싶다면 인자를 두 개 더 넘겨주면 됩니다. 두 번째 인자는 replacer 함수인데(곧 자세히 다룹니다), null을 넘기면 "전부 포함하라"는 뜻입니다. 세 번째 인자는 들여쓰기 크기입니다.
보통 이렇게 들여쓰기해서 예쁘게 출력(pretty-print)하죠. 대부분의 도구가 뱉어내는 형식과 맞추려면 2칸이나 4칸을 쓰면 됩니다.
JSON.parse 사용법: 문자열을 객체로
반대 방향입니다. JSON 문자열을 받아서 자바스크립트 값으로 되돌리는 거죠.
파싱이 끝나면 평범한 객체를 다루듯 쓰면 됩니다. 점 표기법, 대괄호 접근, 배열 메서드까지 전부 그대로 사용할 수 있어요.
JSON.parse는 꽤 까다롭습니다. 아래 같은 경우엔 모두 SyntaxError가 발생해요:
JSON.parse("{name: 'Rosa'}"); // 따옴표 없는 키, 작은따옴표
JSON.parse('{"name": "Rosa",}'); // 끝에 쉼표
JSON.parse("// a comment\n{}"); // 주석은 허용되지 않음
JSON.parse(""); // 빈 문자열
외부에서 들어오는 입력, 즉 fetch 응답이든 파일이든 사용자 입력이든 프로그램 바깥에서 온 데이터를 파싱할 때는 반드시 try/catch로 감싸세요:
왕복 변환에서 살아남지 못하는 값들
JSON이 지원하는 값은 딱 여섯 가지입니다. 문자열, 숫자, 불리언, null, 배열, 그리고 일반 객체. 이 외에 자바스크립트에서 쓰는 나머지 값들은 JSON.stringify와 JSON.parse를 거치면서 사라지거나, 형태가 바뀌거나, 아예 오류를 내버립니다.
어떤 일이 벌어지냐면:
- 함수와 **
undefined**는 객체에서 조용히 사라집니다. 배열 안에서는null로 바뀌는데, JSON 배열은 중간에 빈 자리를 허용하지 않기 때문이죠. Date객체는 내부의toJSON메서드를 통해 ISO 형식의 문자열로 직렬화됩니다. 다시 파싱하면Date가 아니라 문자열이 돌아옵니다.- **
BigInt**는TypeError를 던집니다. JSON 숫자에는 대응되는 타입이 없거든요. - **
Map**과Set, 그리고 순환 참조도 기본적으로는 처리되지 않습니다.
Date를 원래대로 되돌리고 싶다면 JSON.parse의 reviver 함수를 활용하면 됩니다:
reviver는 모든 키/값 쌍에 대해 실행되며, 값을 불러오는 과정에서 자유롭게 변환할 수 있게 해줍니다.
replacer로 직렬화할 값 걸러내기
JSON.stringify의 두 번째 인자를 활용하면 결과물에 어떤 값을 포함시킬지 직접 정할 수 있습니다. 포함하고 싶은 키만 배열로 넘기면 화이트리스트 방식으로 동작합니다:
임의의 로직이 필요하다면 함수를 넘기면 된다. 필드를 빼거나, 값을 마스킹하거나, 즉석에서 변환하는 것까지 전부 가능하다:
undefined를 반환하면 해당 키가 제거되고, 다른 값을 반환하면 그 값으로 대체됩니다.
toJSON 메서드로 직렬화 커스터마이징하기
객체에 toJSON 메서드가 정의되어 있으면, JSON.stringify는 객체 자체가 아니라 이 메서드가 반환하는 값을 직렬화합니다. Date 객체가 자신만의 포맷으로 변환되는 것도 바로 이 원리 덕분이죠. 우리도 똑같은 훅을 활용할 수 있습니다:
클래스를 누가 직렬화하든 일관된 공개 형태를 유지하고 싶을 때 딱 좋은 방법입니다.
깊은 복사(옛날 방식과 더 나은 방식)
예전부터 일반 객체를 깊은 복사할 때는 JSON.parse(JSON.stringify(obj)) 한 줄이 단골 관용구였습니다.
이 방법은 객체 안에 JSON으로 표현 가능한 값만 들어 있을 때에만 제대로 동작합니다. Date, Map, 함수 같은 값들은 모두 문제가 생기죠 (앞서 살펴본 라운드트립 이슈 참고).
최신 자바스크립트에는 structuredClone이 있는데, Date, Map, Set, 타입드 배열은 물론 순환 참조까지 깔끔하게 처리해 줍니다:
가능하면 structuredClone을 먼저 써보세요. JSON.parse(JSON.stringify(...)) 트릭은 평범한 데이터를 빠르게 복사해야 할 때를 위해 주머니에 넣어두는 정도면 충분합니다.
실전 예제: 데이터를 받아와서 파싱하기
실무에서 JSON을 가장 많이 만나는 곳은 역시 HTTP 통신입니다. fetch는 JSON을 자동으로 파싱해주지 않아요. 응답 객체에서 .json()을 호출해야 하는데, 이게 사실상 응답 본문에 JSON.parse를 돌리는 것과 같습니다:
JSON을 보낼 때는 반대로, 본문을 JSON.stringify로 직렬화하고 Content-Type 헤더를 지정하면 됩니다.
await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Rosa", age: 30 }),
});
이 두 가지 패턴만 알아두면 실무에서 마주치는 JSON 작업의 대부분을 처리할 수 있습니다.
다음 주제: 옵셔널 체이닝
파싱한 JSON에는 있을 수도 있고 없을 수도 있는 필드가 자주 등장합니다. user.address.city처럼 존재하지 않을 수 있는 값이나, response.data.items처럼 응답에 포함되지 않을 수 있는 값이 대표적이죠. 이렇게 깊이 중첩된 속성에 접근하면서도 에러를 내지 않는 방법이 바로 옵셔널 체이닝(?.)이고, 다음 페이지에서 자세히 다뤄봅니다.
자주 묻는 질문
JSON과 자바스크립트 객체는 뭐가 다른가요?
JSON은 텍스트 형식, 즉 문자열입니다. 생김새는 자바스크립트 객체 리터럴과 비슷하지만 규칙이 훨씬 엄격하죠. 키는 반드시 큰따옴표로 감싸야 하고, 문자열도 큰따옴표만 허용되며, 값으로 쓸 수 있는 건 문자열·숫자·불리언·null·배열·일반 객체뿐입니다. 반면 자바스크립트 객체는 메모리에 살아있는 실제 값이라서 함수, undefined, Date 인스턴스 등 뭐든 담을 수 있습니다.
자바스크립트 객체를 JSON으로 어떻게 변환하나요?
JSON.stringify(obj)를 호출하면 됩니다. 객체를 순회하면서 JSON 문자열로 만들어 주죠. 보기 좋게 출력하고 싶다면 JSON.stringify(obj, null, 2)처럼 세 번째 인자로 들여쓰기 칸 수를 넘겨주면 됩니다. 참고로 객체 안의 함수나 undefined 값은 아예 빠지고, 배열 안에 있으면 null로 바뀝니다.
JSON.parse에서 에러가 나는 이유는 뭔가요?
JSON.parse는 규칙에 아주 엄격합니다. 마지막에 붙은 쉼표, 작은따옴표, 따옴표 없는 키, 주석(//, /* */) 전부 SyntaxError를 일으켜요. 네트워크 응답이나 파일, 사용자 입력처럼 유효한 JSON이 아닐 가능성이 있는 문자열은 항상 try/catch로 감싸는 게 안전합니다.
JSON.stringify는 Date 객체를 그대로 보존하나요?
아니요. Date는 "2026-01-15T10:30:00.000Z" 같은 ISO 문자열로 바뀝니다. 다시 JSON.parse로 되돌려도 Date 객체가 아니라 그냥 문자열로 돌아와요. 날짜 타입을 꼭 유지해야 한다면 JSON.parse의 reviver 인자를 활용해 ISO 문자열을 Date 인스턴스로 다시 복원해야 합니다.