← 전체 글

JSON vs JavaScript 객체: 작은따옴표가 허용되지 않는 이유

많은 개발자가 JS 객체 리터럴을 JSON으로 취급한다. 같지 않다: 작은따옴표, 따옴표 없는 키, 후행 쉼표, undefined, NaN —— 모든 차이를 예시로.

유효해 보이는 데이터를 JSON 파서에 붙여 넣었는데 즉시 SyntaxError: Unexpected token ''' 가 던져집니다. 그 데이터는 JavaScript 소스에서 왔고 완전히 합리적으로 보입니다 —— 하지만 JSON 과 JavaScript 객체 리터럴은 같은 형식이 아니며, 그 차이는 대부분의 개발자가 인식하는 것보다 더 중요합니다.

짧은 답: JSON 은 작은따옴표를 지원하지 않는다

JSON 에서 작은따옴표는 완전히 불법입니다. 모든 문자열 —— 키와 값 모두 —— 은 큰따옴표를 써야 합니다. 예외 없음.

// ❌ 잘못된 JSON —— 작은따옴표 문자열
{ 'name': 'Ada Lovelace', 'active': true }

// ✅ 유효한 JSON
{ "name": "Ada Lovelace", "active": true }

여러 환경에서 보이는 오류:

// Chrome / Node.js (V8)
SyntaxError: Unexpected token "'", "{'name':..." is not valid JSON

// Firefox
SyntaxError: JSON.parse: unexpected character at line 1 column 2 of the JSON data

// Safari
SyntaxError: JSON Parse error: Single quotes (') are not allowed in JSON

Safari 의 메시지가 가장 도움됩니다 —— 문제가 무엇인지 정확히 알려 줍니다. 나머지는 예상치 못한 문자만 보고합니다.

JSON 은 왜 큰따옴표를 요구하나?

JSON 은 2001 년 Douglas Crockford 가 정의했고, RFC 8259 로 표준화됐습니다. 문법은 정확히 한 가지 문자열 구분자만 지정: 큰따옴표 문자(U+0022). 작은따옴표는 문법에 아예 없습니다.

이유는 단순함입니다. JSON 은 JavaScript 만이 아니라 모든 언어가 파싱하도록 의도된 데이터 교환 형식입니다. 하나의 따옴표 스타일을 강제함으로써 명세는 모호함 한 부류 전체를 없앱니다. Go, Python, Java, Rust 등 다른 모든 언어의 파서가 정확히 같은 규칙을 구현합니다.

JSON vs JavaScript 객체 리터럴: 전체 차이

작은따옴표는 여러 차이 중 하나일 뿐입니다. 전체 그림은 다음과 같습니다:

1. 키는 큰따옴표 문자열이어야 한다

// ❌ JavaScript 객체 리터럴 —— 맨 키, 작은따옴표 키
{ name: "Ada", 'score': 98 }

// ✅ JSON —— 모든 키는 큰따옴표 문자열
{ "name": "Ada", "score": 98 }

JavaScript 객체 리터럴은 맨 식별자, 작은따옴표 문자열, 큰따옴표 문자열을 키로 받아들입니다. JSON 은 큰따옴표 문자열만 받아들입니다.

2. 후행 쉼표 불가

// ❌ 잘못된 JSON
{ "name": "Ada", "score": 98, }
//                            ^ 후행 쉼표

// ✅ 유효한 JSON
{ "name": "Ada", "score": 98 }

현대 JavaScript 는 명시적으로 후행 쉼표를 허용. JSON 은 안 됨 —— JSON 후행 쉼표에 대한 전체 가이드 참고.

3. 주석 없음

// ❌ 잘못된 JSON
{
  // 사용자 프로필
  "name": "Ada"
}

// ✅ JSON 에는 주석 문법이 없음
{ "name": "Ada" }

JSON 에는 어떤 주석 문법도 없습니다. 설정 파일에 주석이 필요하면 VS Code 설정, TypeScript 의 tsconfig.json, 그리고 여러 설정 파일 파서가 지원하는 JSONC 형식(JSON-with-Comments)을 고려하세요.

4. undefined, 함수, Symbol 없음

// JavaScript 객체 —— 직렬화 불가능한 값을 받아들임
const obj = {
  fn: () => 42,         // 함수
  sym: Symbol('id'),    // Symbol
  val: undefined,       // undefined
};

// JSON.stringify 가 조용히 떨어뜨리거나 변환
JSON.stringify(obj);
// → '{}'   (세 속성이 사라지거나 null 이 됨)

JSON 은 정확히 여섯 가지 값 타입을 지원: string, number, boolean(true/false), null, object, array. 함수, Symbol, undefined 는 JSON 표현이 없습니다. JSON.stringify() 는 이런 값을 가진 객체 속성을 조용히 떨어뜨리고 배열 안에서는 null 로 바꿉니다.

5. NaN 이나 Infinity 없음

JSON.stringify({ ratio: NaN, limit: Infinity });
// → '{"ratio":null,"limit":null}'
// NaN 과 Infinity 가 null 이 됨 —— 조용히!

JSON.parse('{"ratio": NaN}');
// → SyntaxError: Unexpected token 'N'

NaNInfinity 는 유효한 JavaScript 숫자 값이지만 JSON 숫자 명세의 일부가 아닙니다. JSON.stringify() 는 나갈 때 null 로 바꾸고, JSON.parse() 는 들어올 때 거부. 이 비대칭은 미묘한 버그의 흔한 원인입니다.

6. 숫자: 16 진수 없음, 선행 0 없음, + 없음

// ❌ 잘못된 JSON
{ "flags": 0xFF, "code": 007, "delta": +1.5 }

// ✅ 유효한 JSON
{ "flags": 255, "code": 7, "delta": 1.5 }

JSON 숫자는 10 진수여야 하고, 명시적 + 부호로 시작할 수 없으며, 선행 0 도 안 됩니다(소수점 앞의 단일 0 제외). 16 진수와 8 진수 리터럴은 유효한 JSON 이 아닙니다.

7. 여러 줄 문자열 없음

// ❌ 잘못된 JSON —— 문자열 안의 리터럴 줄바꿈
{ "bio": "Line one
Line two" }

// ✅ 이스케이프 시퀀스 사용
{ "bio": "Line one\nLine two" }

8. 계산된 키, Symbol 키, 메서드 단축

JSON 처럼 보이지만 대응물이 없는 JS 전용 기능 세 가지 더:

// ❌ 계산된 속성 이름 —— 평가됨, 리터럴 아님
const k = "id";
const obj = { [k]: 1, [`user_${k}`]: 1 };
// JSON 에는 표현식이 없습니다; 키는 리터럴 큰따옴표 문자열이어야 합니다.

// ❌ Symbol 키 —— JSON.stringify 가 조용히 떨어뜨림
const tag = Symbol('tag');
JSON.stringify({ [tag]: 'admin' });  // → '{}'

// ❌ 메서드 단축 —— 함수는 떨어뜨려짐/생략됨
const obj = { greet() { return 'hi'; } };
JSON.stringify(obj);  // → '{}'

이 중 하나가 필요하면 객체의 JSON 모양 투영을 직렬화하세요 —— JSON.stringify() 는 언제나 문자열 키와 여섯 가지 JSON 값 타입만 생성합니다.

빠른 변환 치트시트

JavaScript 객체 리터럴유효한 JSON 등가
{ name: "Ada" }{"name":"Ada"}
{ 'name': 'Ada' }{"name":"Ada"}
{ a: 1, }{"a":1}
{ val: undefined }{} (속성 떨어짐)
{ n: NaN }{"n":null}
{ n: Infinity }{"n":null}
{ fn: () => 1 }{} (속성 떨어짐)
0xFF255

JS 객체를 JSON 으로 변환하는 가장 안전한 방법

따옴표를 손으로 편집해 JavaScript 객체를 JSON 으로 변환하지 마세요. JSON.stringify() 를 쓰세요 —— 모든 경계 사례를 정확히 다룹니다:

const obj = { name: 'Ada', score: 98, active: true };

// 컴팩트
JSON.stringify(obj);
// → '{"name":"Ada","score":98,"active":true}'

// 2 스페이스 들여쓰기로 예쁘게 출력
JSON.stringify(obj, null, 2);
// → '{
  "name": "Ada",
  "score": 98,
  "active": true
}'

JSON.stringify() 의 두 번째 인수는 replacer 입니다 —— 키 이름 배열을 넘겨 그 속성만 포함하거나, 함수를 넘겨 값을 변환할 수 있습니다:

// 특정 키만 포함
JSON.stringify(obj, ['name', 'score'], 2);
// → '{
  "name": "Ada",
  "score": 98
}'

// 커스텀 replacer —— undefined 를 null 로 변환
JSON.stringify({ a: 1, b: undefined }, (key, value) =>
  value === undefined ? null : value
);
// → '{"a":1,"b":null}'

JS 객체 리터럴을 문자열로 받았을 때

때로는 JSON 처럼 보이지만 실제로는 JavaScript 객체 리터럴인 데이터를 받게 됩니다 —— 로그 파일, 디버깅 도구, 명세를 따르지 않은 API 등에서. 그러면 JSON.parse() 가 실패하고 두 가지 선택지가 있습니다:

  • 수리 파서 사용 —— 작은따옴표를 큰따옴표로 변환, 후행 쉼표 제거, 맨 키에 따옴표 추가, 위에 언급한 다른 모든 차이를 자동 처리. 신뢰할 수 없는 입력에 가장 안전.
  • eval() 또는 Function() 사용 —— 기술적으로 유효한 JavaScript 에 동작하지만, 소스가 완전히 신뢰되지 않으면 심각한 보안 위험. 사용자 제공 입력엔 절대 금지.

자주 묻는 질문

JSON 은 작은따옴표를 지원하나요?

아니요. JSON 문법(RFC 8259)은 정확히 하나의 문자열 구분자 —— 큰따옴표 —— 만 정의합니다. 키와 값 모두 큰따옴표를 써야 하며, 작은따옴표는 SyntaxError 를 던집니다.

JSON 과 JavaScript 객체의 차이는?

JSON 은 엄격한 텍스트 형식; JavaScript 객체 리터럴은 관대한 코드입니다. JS 는 작은따옴표, 따옴표 없는 키, 후행 쉼표, 주석, undefined, NaN, 함수, 16 진수 숫자를 허용 —— 그 어느 것도 유효한 JSON 이 아닙니다. 전체 치트시트는 위에, 입문은 JSON 이란?

JavaScript 객체를 JSON 으로 어떻게 변환하나요?

JSON.stringify() 를 쓰세요 —— 절대 따옴표를 손으로 편집하지 마세요. 모든 경계 사례를 다루고 직렬화 불가능한 값을 떨어뜨리고 문자열을 정확히 이스케이프합니다.

왜 eval() 로 JS 객체 리터럴을 파싱하면 안 되나요?

eval() 은 임의 코드를 실행하므로 신뢰할 수 없는 입력엔 심각한 보안 위험. 대신 수리 파서(또는 JSON Fix)로 JS 객체 리터럴을 유효한 JSON 으로 안전하게 변환하세요.

작은따옴표 즉시 수정

지금 유효한 JSON 으로 변환해야 할 JavaScript 객체 리터럴이 있다면 JSON Fix 가 자동으로 변환을 처리합니다. 작은따옴표를 큰따옴표로 변환, 맨 키에 따옴표 추가, 후행 쉼표 제거, 위에 나열된 모든 다른 차이를 고침 —— 브라우저에서, 어떤 서버에도 데이터를 보내지 않고.