JSON 오류 메시지 조회 - 오류 문구에서 해결책까지
핵심 요약: JSON.parse 메시지를 기준으로 원인을 조회합니다. Chrome, Firefox, Safari에서 자주 나오는 12가지 오류와 HTML 오류 페이지, 잘림, 이중 파싱까지 다룹니다.

핵심부터 말하면, JSON.parse 오류가 나면 먼저 position과 token을 확보하세요. position이 0이면 HTML 응답, BOM, 잘못된 타입을 확인하고, "Expected" 메시지라면 앞 문자를 보세요. 보고된 위치부터 줄줄이 다시 읽지 마세요.
오류 문구는 잡음이 아니라 정답입니다. 파서는 어떤 문자를 만났고, 무엇을 기대했고, 실제로 무엇이 왔는지를 알려 줍니다. 오류 메시지로 검색하는 편이 문서 전체를 손으로 다시 쓰는 것보다 빠릅니다.
모든 JSON.parse() 오류에는 세 부분이 있습니다. position(문자 오프셋 또는 행/열), 기대(Expected...), 현실(Unexpected token X 또는 xxx is not valid JSON)입니다. 엔진마다 표현이 다릅니다. Chrome과 Node(V8)는 Expected ',' or '}' after property value in JSON at position 25, Firefox는 JSON.parse: expected ',' or '}' after property value in object at line 3 column 1, Safari는 JSON Parse error: Expected ',' or '}'라고 하지만, 셋 모두 같은 문법 파손을 가리킵니다. 오류를 조회할 때는 문구를 무시하고 세 가지 사실만 잡으세요. 어떤 문자인지, 무엇을 기대했는지, 무엇이 도착했는지입니다.
1. 12가지 오류 조회 표
왼쪽 열은 콘솔에서 가장 식별하기 쉬운 단편(V8 표현)입니다. Firefox/Safari 변형은 괄호에 적었습니다.
| 오류 문구 (V8) | 실제 원인 | 해결 |
|---|---|---|
Unexpected token < in JSON at position 0 |
API가 JSON이 아닌 HTML을 반환함(404 페이지, 500 템플릿, 로그인 리다이렉트, 프록시 폴백) | 먼저 response.status와 Content-Type 확인. JSON을 고치지 말 것 |
Unexpected end of JSON input |
입력이 비었거나 잘렸음: 빈 본문, 204, 중단된 다운로드, 마지막 줄이 빠진 복사 | 빈 경우를 처리하고 Content-Length와 비교해 잘린 지점 찾기 |
Unexpected token o / "[object Object]" is not valid JSON |
이미 파싱된 객체를 JSON.parse()에 다시 넘겨 문자열로 강제 변환됨 |
typeof 확인. 객체라면 그대로 사용 |
"undefined" is not valid JSON |
변수에 값이 할당된 적 없음(JSON.parse(undefined)) |
기본값 초기화, 파싱 전 가드 |
Expected double-quoted property name in JSON |
후행 쉼표({"a": 1,}) 때문에 파서가 다음 키를 기대하다 }를 만남 |
마지막 항목 뒤 쉼표 제거 |
Expected property name or '}' in JSON |
따옴표가 없거나 작은따옴표인 키({name: "Ada"}, {'name': 'Ada'}) |
모든 키에 큰따옴표 |
Expected ',' or '}' after property value in JSON |
쉼표 누락({"a": 1 "b": 2}) 또는 값 뒤의 주석 |
쉼표 추가, 주석 제거 |
Unexpected token '/' |
JSON 안의 // 또는 /* */ 주석 |
제거하거나 설정을 JSONC로 파싱 |
Bad control character in string literal |
문자열 안의 실제 줄 바꿈, 탭, 기타 U+0000–U+001F 제어 문자 | \n으로 작성, JSON.stringify로 생성 |
Unterminated string in JSON |
"로 시작한 문자열이 닫히지 않았거나 내부 따옴표가 이스케이프되지 않음 |
내부 따옴표를 \"로 |
Unexpected non-whitespace character after JSON |
한 텍스트에 두 문서: {}{}, NDJSON, JSONP 래퍼 |
줄 단위로 나눠 각각 파싱하거나 NDJSON 파서 사용 |
JSON.parse is not a function |
직접 만든 변수가 전역 JSON을 가림 |
변수 이름 변경. JSON이라는 이름은 쓰지 말 것 |
앞의 두 가지는 더 자세히 볼 가치가 있습니다. 둘 다 "당신의 JSON이 틀렸다"가 아니라 "받은 것이 생각한 것이 아니다"를 뜻하기 때문입니다.
2. position 0 오류의 흔한 세 가지 원인
position이 0이면 첫 문자부터 불법이라는 뜻입니다. 의심할 만한 것이 없어 보인다면 그 문자는 아마 보이지 않는 문자입니다. 흔한 용의자는 셋입니다:
첫째, <. HTML이라는 뜻입니다. 운영 환경에서 가장 흔한 파싱 오류입니다. 잘못된 URL, 게이트웨이가 리다이렉트한 만료 세션, 서버 오류 템플릿 등으로 본문이 <!DOCTYPE html>로 시작하고 파서는 첫 문자에서 멈춥니다. 올바른 행동은 Network 패널을 열어 원시 응답을 읽고, JSON이 아니라 API 호출을 고치는 것입니다.
둘째, BOM 또는 너비 0 문자. Windows 메모장으로 저장한 파일은 흔히 UTF-8 BOM(\uFEFF)으로 시작하고, 웹 페이지나 채팅 앱에서 복사한 텍스트에는 너비 0 공백(\u200B–\u200D)이 붙을 수 있습니다. 당신에게는 보이지 않지만 파서에게는 명백합니다:
// Strip BOM and zero-width characters before parsing
const cleaned = raw.replace(/^\uFEFF/, '').replace(/[\u200B-\u200D]/g, '');
const data = JSON.parse(cleaned);
셋째, 잘못된 타입. JSON.parse(undefined)는 "undefined" is not valid JSON을 보고하고, JSON.parse(someObject)는 Unexpected token o를 보고합니다(객체가 "[object Object]"로 강제 변환되어 position 1의 o가 불법입니다). 둘 다 "데이터가 이미 파싱되었다" 또는 "데이터가 애초에 없었다"를 뜻합니다. 문법이 아니라 타입을 확인하세요:
let data = text;
if (typeof data === 'string') {
data = JSON.parse(data); // Parse only when it really is a string
}
3. position은 하류를 가리키고, 원인은 상류에 있다
위치를 오해하게 만드는 오류가 둘 있는데, 표에서 가장 잘못 읽히는 두 항목입니다:
- 쉼표 누락:
{"a": 1 "b": 2}는"b"행에서Expected ',' or '}' after property value를 보고합니다. 보고는 맞지만 고칠 곳은 앞 행의 끝입니다. - 닫는 괄호 누락: 깊은 중첩에서
}나]하나가 빠지면 파서가 입력 끝까지 삼켜 버리고Unexpected end of JSON input을 보고합니다. 위치는 파일 끝이지만 실제 누락 지점은 훨씬 앞일 수 있습니다. **가장 최근에 닫히지 않은{나[**를 역방향으로 찾으세요. 짝을 세는 것이 눈으로 훑는 것보다 낫습니다.
일반적인 기법은 이분 탐색입니다. 보고된 위치 앞까지 잘라 단독으로 파싱합니다. 파싱되면 문제는 보고된 위치 근처에 있고, 파싱되지 않으면 더 앞으로 이분합니다. 처음 실패하는 조각의 경계가 실제 오류 지점입니다. 큰 파일이라면 먼저 문서를 포매팅하세요. 들여쓰기가 맞으면 빠진 단계가 한눈에 보입니다(온라인 JSON 포매터로 충분합니다).
4. 파싱은 되지만 동작이 잘못되는 네 가지 "겉보기 정상" 상태
오류보다 나쁜 것은 오류가 없는데 데이터가 틀린 경우입니다. 네 경우 모두 JSON은 유효하고 파서는 만족합니다. 문제는 데이터의 모양입니다:
- 이중 인코딩: 문서 전체를
JSON.stringify로 두 번 감싸서 파싱 결과가 객체가 아니라 문자열입니다.typeof result === 'string'이면 다시 파싱하되, 근원에서 이중 직렬화를 찾아 고치세요. - 중복 키의 조용한 덮어쓰기:
{"name": "Tom", "name": "Jerry"}는 유효한 JSON이고 대부분의 파서는 불평하지 않으며 두 번째 값이 이깁니다. 여러 출처의 데이터를 병합할 때 물립니다. 물리기 전에 엄격한 JSON 검증기로 드러내세요. - NDJSON을 하나의 문서로 취급: 스트리밍 API는 한 줄에 객체 하나를 내보내고, 전체 텍스트를 파싱하면
Unexpected non-whitespace character after JSON으로 실패합니다.\n으로 나눠 줄마다 파싱하세요. - 숫자 정밀도의 조용한 손실:
2^53 - 1을 넘는 정수(스노우플레이크 ID)는 오류 없이 마지막 자릿수가 0으로 돌아옵니다. ID는 문자열로 전송하세요.
공통점은 파서가 모든 것이 정상이라고 믿는다는 것입니다. 그래서 API 데이터가 이상해 보이면 먼저 원시 텍스트의 앞 80자와 typeof를 출력하세요. 내용을 논하기 전에 모양을 확인하는 것입니다.
5. 작성 시점에 오류를 불가능하게 만들기
조회 표는 사후 도구이고, 예방은 사전 도구입니다. 네 가지 습관이 대부분의 오류를 발생 전에 차단합니다:
// 1. Two checks before parsing any response
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: the API returned no data`);
}
const contentType = response.headers.get('content-type') ?? '';
if (!contentType.includes('application/json')) {
throw new Error(`Expected JSON, got ${contentType}`);
}
const data = await response.json(); // 2. json() already parses — do not parse again
- 직렬화로 생성하고 절대 이어 붙이지 않기:
JSON.stringify()는 따옴표, 줄 바꿈, 제어 문자를 올바르게 처리합니다. 손으로 만든 문자열은Bad control character와Unterminated string의 최대 근원입니다. - 항상 catch하기:
JSON.parse오류는 누군가 잡지 않으면 동기 크래시입니다. localStorage, 외부 파일 같은 신뢰할 수 없는 입력에는 catch 블록이 기본값이나 명시적 오류 보고를 제공해야 합니다. - 구조는 Schema에 맡기기: 문법이 유효하다고 구조가 유효한 것은 아닙니다. 필드 누락과 타입 불일치는 경계에서의 JSON Schema 검증에 속합니다. JSON Schema가 해결하는 것을 참고하세요.
6. 빠른 판단 경로
글 전체를 하나의 판단 순서로 압축합니다. 오류를 보면 먼저 position과 token을 잡으세요:
- position 0 → 세 가지 원인 확인:
<(HTML), BOM, 잘못된 타입 end of input→ 빈 본문이나 잘린 본문 확인, 이어서 닫히지 않은 괄호를 역방향 탐색Expected ...→ 앞 문자 확인. 대개 쉼표나 따옴표 누락after JSON data→ 여러 문서가 이어 붙음. 줄 단위로 분리- 파싱은 되는데 데이터가 이상함 → 네 가지 "겉보기 정상" 점검 실행
JSON 파싱 오류 해결법의 단계별 분류와 결합하면 파싱 문제의 90%가 5분 안에 해결됩니다. 오류 문구 자체를 북마크해 두세요. 파서가 당신을 위해 쓴 디버깅 로그이고, 문구는 3년에 한 번 바뀌며, 한 번 조회하면 1년이 득입니다.
"Unexpected token in JSON"은 무슨 뜻인가?
파서가 JSON 문법상 그 위치에서 불법인 문자를 만났다는 뜻입니다. token은 거부된 문자입니다. <는 보통 API가 HTML을 반환했다는 뜻이고, '는 작은따옴표, /는 주석, 일반 글자는 대개 따옴표 없는 키나 undefined, NaN 같은 JavaScript 전용 리터럴입니다. 먼저 token을 읽고 다음 position을 보면 원인이 하나로 좁혀집니다.
"Unexpected end of JSON input"은 어떻게 고치나?
원인은 둘입니다. 입력이 비었거나(빈 응답 본문, localStorage의 빈 문자열) 입력이 잘렸기 때문(중단된 다운로드, 마지막 줄이 빠진 복사, 크기 제한)입니다. 빈 경우는 if (!text) return으로 처리하고, 그다음 완전성을 확인해 가장 최근에 닫히지 않은 {, [, "를 닫으세요. 깊은 곳에서 괄호가 빠져도 이 오류가 나올 수 있으며, 위치가 우연히 파일 끝인 경우도 있습니다.
Chrome, Firefox, Safari가 오류를 다르게 보고하는 이유는?
세 브라우저는 서로 다른 JS 엔진(V8, SpiderMonkey, JavaScriptCore)을 쓰고 각자 방식으로 오류를 표현합니다. MDN이 모든 공식 변형을 문서화해 두었습니다. 세 오류 모두 position, 기대, 현실이라는 같은 세 요소를 담고 의미는 일대일로 대응합니다. V8 표현으로 옮겨 이 표를 그대로 쓰세요.
오류 위치가 멀쩡해 보이는 곳을 가리키면?
실제 오류는 더 앞에 있습니다. 대표적인 경우는 쉼표 누락(다음 행에서 보고됨)과 닫는 괄호 누락(파일 끝에서 보고됨)입니다. 보고된 위치에서 token 단위로 역방향으로 걸어가거나, 이분 탐색하세요. 앞 절반을 단독 파싱하고 처음 실패하는 조각의 경계가 진짜 오류 지점입니다.