모든 JavaScript 개발자가 본 적이 있는 장면: JSON.parse() 가 SyntaxError 를 던지고 애플리케이션이 죽습니다. 그 JSON 은 API, 설정 파일, 또는 사용자의 클립보드에서 왔는데 —— 그냥 파싱이 안 됩니다. 이 가이드에서는 왜 그런 일이 일어나는지, 실제 깨진 JSON 은 어떻게 생겼는지, 어떻게 우아하게 처리할지 단계별로 살펴봅니다 —— 간단한 try/catch 부터 자동 수리까지.
1. 왜 JSON.parse() 가 실패하나
JSON 표준(RFC 8259)은 의도적으로 엄격합니다. JSON.parse() 가 그것을 정확히 구현 —— 아무리 작은 편차라도 부분 복구 없이 SyntaxError 를 던집니다:
try {
const data = JSON.parse('{ name: "Ada" }'); // 따옴표 없는 키
} catch (err) {
console.error(err.message);
// SyntaxError: Expected property name or '}' in JSON at position 2
}HTML 을 파싱하는 브라우저(오류 복구 모델이 내장)와 달리 JSON 파서는 단단한 게이트. 엄격한 파싱은 기능: 생산자에게 깨끗한 데이터를 보내라고 강제. 하지만 데이터 출처를 통제할 수 없을 때 —— LLM 출력, 손 편집된 설정 파일, 서드파티 API —— 그 게이트가 쾅 닫힐 때를 위한 전략이 필요합니다.
2. 가장 흔한 깨진 JSON 패턴
수리 라이브러리로 손을 뻗기 전에 무엇을 다루고 있는지 알아보는 게 도움됩니다. 실제 깨진 JSON 의 거의 모두가 다음 카테고리 중 하나에 속합니다:
큰따옴표 대신 작은따옴표
// ❌ 잘못된 JSON
{ 'name': 'Ada Lovelace' }
// ✅ 유효
{ "name": "Ada Lovelace" }JavaScript 객체 리터럴은 작은따옴표를 받지만 JSON 은 안 받습니다. 개발자가 객체 리터럴을 JSON 컨텍스트에 그대로 복사해 이 오류에 끝없이 부딪힙니다.
후행 쉼표
// ❌ 잘못된 JSON
{
"name": "Ada",
"active": true, // 후행 쉼표
}
// ✅ 유효
{
"name": "Ada",
"active": true
}현대 JavaScript 는 배열과 객체에 후행 쉼표를 허용. JSON 은 안 함. 손으로 편집한 JSON 파일에서 가장 흔한 단일 오류.
따옴표 없는 객체 키
// ❌ 잘못된 JSON
{ name: "Ada", score: 98 }
// ✅ 유효
{ "name": "Ada", "score": 98 }JavaScript 객체 리터럴은 따옴표 있는 키를 요구하지 않음. JSON 은 모든 키를 큰따옴표 문자열로 요구, 예외 없음.
JavaScript 주석
// ❌ 잘못된 JSON
{
// 이건 사용자 레코드
"name": "Ada",
/* 2024 추가 */
"active": true
}JSON 에는 주석 문법이 없음. 주석이 설정 파일에 더해지곤 함(JSONC —— 주석 있는 JSON —— 은 인기 확장) 하지만 JSON.parse() 가 거부합니다.
여러 줄 문자열
// ❌ 잘못된 JSON
{
"description": "line one
line two"
}
// ✅ 유효 —— 줄 바꿈 이스케이프
{
"description": "line one\nline two"
}JSON 의 문자열 값은 한 줄이어야 함. 문자열 안의 리터럴 줄바꿈은 허용 안 됨; 대신 \n 이스케이프 시퀀스 사용.
NaN, Infinity, undefined
// ❌ 유효한 JSON 아님
{ "ratio": NaN, "limit": Infinity, "value": undefined }
// ✅ 유효한 대안
{ "ratio": null, "limit": 1e308, "value": null } 이들은 유효한 JavaScript 값이지만 JSON 명세의 일부가 아님. JSON.stringify() 가 조용히 변환(NaN 과 Infinity 는 null 이 되고; undefined 속성은 통째로 떨어뜨려짐), 미묘한 왕복 버그를 일으킬 수 있음.
Python 리터럴
// ❌ Python 스타일 —— 잘못된 JSON
{ "active": True, "deleted": False, "value": None }
// ✅ 유효한 JSON
{ "active": true, "deleted": false, "value": null } JSON 은 여러 언어 간 상호 운용을 위해 설계됨. Python 의 대문자 시작 True, False, None 은 언어 간 데이터 버그의 흔한 원천.
3. "수리" 와 "파싱" 은 같은 것이 아니다
근본적으로 다른 두 동작을 구분하는 것이 중요:
- 엄격 파싱 —— 입력이 정확히 올바른 JSON 인지 검증, 모든 오류의 정확한 위치를 보고, 어떤 편차든 거부. 데이터 생산자를 통제하고 계약을 강제하고 싶을 때 사용.
- 수리 파싱 —— 휴리스틱 규칙 세트를 사용해 구문적으로 불완전한 입력에서 유효한 JSON 값을 복구 시도. 신뢰할 수 없거나 부정확한 출처(LLM 출력, 사용자 클립보드, 레거시 서비스) 에서 데이터가 올 때 사용.
수리 파서는 가정을 합니다. True 를 만나면 true 를 의미했다고 가정. 후행 쉼표를 만나면 제거. 위 패턴들에 대해 이 가정들은 거의 항상 옳지만 —— 진짜로 잘못된 문서를 조용히 가려서 "수정" 이 의도된 의미를 바꿀 수도 있음.
올바른 도구는 맥락에 달려 있음. 스키마 검증된 API 응답 파이프라인에서는 엄격 파싱이 좋음, 나쁜 데이터가 즉시 표면화. 사람이 Slack 메시지에서 복사한 JSON 을 붙여 넣는 개발자 도구에선 수리 파싱이 시간을 절약.
알아 둘 만한 라이브러리
두 npm 패키지가 무거운 일의 대부분을 맡고 상호 보완적인 트레이드오프를 가짐:
jsonrepair—— 관대한 수리 파서. 나쁜 JSON 을 주면 최선을 다해 유효한 문자열을 돌려줌. 위 패턴들 처리(작은따옴표, 후행 쉼표, 따옴표 없는 키, 주석, Python 리터럴, markdown 펜스, 닫히지 않은 괄호, 부분 입력). 동기, 의존성 제로, 작음 —— 메모리에 전체 문서가 있을 때 이상적.clarinet—— SAX 스타일 스트리밍 JSON 파서. JSON 이 청크로 도착(스트림, 큰 파일, LLM 의 토큰 단위 응답) 하고 전체 문서를 기다리는 대신 키/값이 나타날 때마다 반응하고 싶을 때 사용. 구문에 엄격하므로 출처가 신뢰할 수 없으면jsonrepair와 짝지으세요.
경험칙: 작고 신뢰할 수 없는 blob 이 있으면 수리; 문서가 크거나 점진적으로 도착하면 스트림.
4. 오류 잡고 사용자에게 친절한 피드백
최소 실용 패턴은 try/catch. 항상 이렇게 하세요 —— JSON.parse() 가 잡히지 않은 채로 던지게 하지 마세요:
function safeParseJson(text) {
try {
return { ok: true, value: JSON.parse(text) };
} catch (err) {
return { ok: false, error: err.message };
}
}
const result = safeParseJson(userInput);
if (!result.ok) {
showError(`Could not parse JSON: ${result.error}`);
} JSON.parse() 의 오류 메시지는 JavaScript 엔진마다 다름. V8(Node.js, Chrome) 는 위치 힌트 포함:
// V8 오류 메시지
"Expected ',' or '}' after property value in JSON at position 42"
// Firefox SpiderMonkey
"JSON.parse: expected ',' or '}' after property value in object
at line 3 column 5 of the JSON data"위치 정보는 유용하지만 엔진별. 환경 간 신뢰할 수 있는 줄/열 보고가 필요하면 커스텀 재귀 하강 파서가 사용자에게 보여 주기 훨씬 쉬운 구조화 오류를 낼 수 있음.
5. 자동 수리 vs. 확인 요청 언제?
모든 수리가 같은 위험을 가지는 건 아님. 실용적 휴리스틱:
- 자동 수리 안전 —— 후행 쉼표, 공백 정규화, 따옴표 스타일 변환, 따옴표 없는 키, JavaScript 주석, Python boolean/null 리터럴. 의미적 모호함 없는 기계적 변환.
- 사용자에게 물어 보는 것 고려 —— 구조적 수리, 예컨대 닫히지 않은 객체나 배열의 자동 닫기(
{"name": "Ada"→{"name": "Ada"}), 또는 의도적으로 보이는 내용 제거(의도적으로 보이는 주석 제거). 수리는 아마 옳지만 사용자가 확인하고 싶을 수 있음. - 항상 표시, 조용히 수정 절대 안 됨 ——
NaN → null같은 타입 강제는 데이터 값을 바꿈. 사용자에게 무엇이 바뀌었는지 보여 주세요.
좋은 수리 UI 는 diff 를 보여 줌: 왼쪽엔 원본 입력, 오른쪽엔 수리된 출력. 사용자가 결과를 복사하거나 제출하기 전에 수정이 옳은지 확인 가능.
6. 브라우저 내 JSON 처리의 프라이버시 이점
브라우저에서 JSON 을 로컬로 처리할 때 —— 페이지를 이미 실행 중인 JavaScript 엔진을 사용 —— 데이터가 사용자의 기기를 떠나지 않음. 이건 개발자들이 흔히 인식하는 것보다 더 중요:
- API 키와 자격 증명 —— 개발자가 시크릿이 든 JSON 페이로드를 정기적으로 붙임. 서버 사이드 도구는 모든 요청을 로그.
- PII 와 건강 데이터 —— 데이터 전송이 없을 때 GDPR 과 HIPAA 준수가 훨씬 단순.
- 기업 데이터 —— 많은 보안 정책이 내부 데이터를 서드파티 web 서비스에 붙이는 것을 금지.
브라우저 내 처리는 위험 제로의 JSON 수리를 제공: 서버 없음, 로그 없음, 데이터 보존 없음. 계산은 <textarea> 와 몇 KB 의 JavaScript 안에서 일어남.
자주 묻는 질문
JavaScript 에서 JSON.parse 오류를 어떻게 처리하나요?
호출을 try/catch 로 감싸고 SyntaxError 가 전파되게 두지 말고 구조화 결과를 반환(위 safeParseJson 헬퍼 참조). 신뢰할 수 없는 입력에 가드 없이 JSON.parse() 를 절대 호출하지 마세요.
깨진 JSON 을 자동으로 파싱할 방법이 있나요?
네 —— 수리 파서가 휴리스틱(후행 쉼표 제거, 작은따옴표 변환, True 소문자화)을 적용해 불완전한 입력에서 유효한 값을 복구. LLM 출력이나 붙여 넣은 텍스트 같은 신뢰할 수 없는 출처에 사용; 나쁜 데이터를 표면화시키고 싶은 계약 검증된 API 응답에는 쓰지 마세요.
JSON 을 언제 수리하고 언제 거부할까요?
사람이 대략적인 JSON 을 붙여 넣었거나 LLM 이 생성했을 때 수리; 생산자를 통제하고 스키마를 강제하고 싶을 때 거부(엄격 파싱). NaN → null 같은 타입 강제는 항상 표시하고 조용히 적용하지 마세요.
깨진 JSON 의 가장 흔한 원인은?
JavaScript 객체 리터럴을 JSON 으로 취급하는 것 —— 작은따옴표, 따옴표 없는 키, 후행 쉼표. JSON vs JavaScript 객체 와 [object Object] 및 기타 오류 에서 구문 오류 참조.
7. 지금 시도해 보세요 —— 깨진 JSON 을 붙여 넣기
지금 고쳐야 할 잘못된 JSON 문자열이 있다면 JSON Fix 가 위의 모든 패턴 —— 작은따옴표, 후행 쉼표, 따옴표 없는 키, Python 리터럴, 주석, markdown 코드 펜스 등을 처리합니다. 모든 게 브라우저에서 실행; 서버로 아무것도 전송되지 않음.
- JSON Fix —— 깨진 JSON 을 붙여 넣고, 즉시 깨끗한 JSON 을 받기
- 온라인으로 JSON 고치기 —— 온라인 JSON 수리 도구가 어떻게 동작하는지와 언제 쓸지
- LLM 의 JSON 출력 수리 —— AI 생성 JSON 수정용 대상 가이드
- JSON Formatter vs JSON Repair —— 어느 도구를 언제 쓸지
- JSON Diff —— 두 JSON 문서를 나란히 비교해 무엇이 바뀌었는지 정확히 확인