JSON 파싱 오류 해결 — Unexpected token 메시지별 원인과 조치
JSON 오류 메시지는 짧은 영어 한 줄이라 막막해 보이지만, 실제로 마주치는 원인은 몇 가지로 정해져 있습니다. 서버가 JSON 대신 HTML 을 돌려준 경우, 읽을 내용이 비어 있는 경우, 그리고 JavaScript 코드 쓰듯 JSON 을 손으로 쓴 경우가 대부분입니다. Chrome·Node.js·Python 에서 실제로 출력되는 메시지를 그대로 옮기고, 메시지마다 무엇을 확인하면 되는지 정리했습니다.
붙여넣은 JSON 을 정렬하고, 오류가 있으면 메시지와 줄·칸 위치를 짚어 해당 줄을 강조합니다. 브라우저 안에서만 처리해 데이터가 밖으로 나가지 않습니다.
오류 메시지 읽는 법
같은 JSON 이라도 어디서 읽느냐에 따라 메시지 문구가 다릅니다. 아래는 끝에 쉼표가 남은 {"name": "kim", "age": 20,} 를 환경별로 읽었을 때 실제로 나온 메시지입니다.
| 환경 | 메시지 |
|---|---|
| Chrome·Edge·Node.js | Expected double-quoted property name in JSON at position 26 (line 1 column 27) |
Python json | Expecting property name enclosed in double quotes: line 1 column 27 (char 26) |
| 예전 버전 Chrome·Node.js | Unexpected token } in JSON at position 26 |
숫자를 읽는 규칙은 두 가지입니다. position 과 파이썬의 char 는 0부터 센 글자 위치이고, line·column 은 에디터처럼 1부터 셉니다. 그래서 position 26 과 column 27 은 같은 자리를 가리킵니다.
메시지가 가리키는 곳은 '파서가 더는 읽을 수 없게 된 자리'입니다. 실수는 보통 그보다 앞에 있으니, 표시된 위치에서 왼쪽으로 거슬러 올라가며 보세요.
Unexpected token '<' — 서버가 JSON 대신 HTML 을 보냈다
fetch 로 API 를 부르고 res.json() 을 했을 때 가장 자주 보는 오류입니다. 브라우저마다 문구는 달라도 뜻은 같습니다.
Chrome·Edge : SyntaxError: Failed to execute 'json' on 'Response': Unexpected token '<', "<!DOCTYPE "... is not valid JSON
Firefox : SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
Safari : SyntaxError: JSON Parse error: Unrecognized token '<'< 는 <!DOCTYPE html> 의 첫 글자입니다. JSON 이 와야 할 자리에 HTML 문서가 왔다는 뜻이라, JSON 을 만드는 코드를 아무리 들여다봐도 답이 나오지 않습니다. 원인은 대개 다음 중 하나입니다.
- API 주소 오타나 경로 변경 — 서버가 404 오류 페이지(HTML)를 응답
- 서버 내부 오류 — 500 오류 페이지가 HTML 로 옴
- 로그인 세션 만료 — API 대신 로그인 페이지로 리다이렉트됨
- 개발 서버 프록시 미설정 — React·Vue 개발 서버가
/api/...요청에index.html을 돌려줌 - 정적 호스팅의 SPA 설정 — 없는 경로에도 모두
index.html을 응답
확인 방법
- 1개발자 도구(F12) → Network 탭 → 해당 요청을 클릭하고 Response 를 봅니다. HTML 이 보이면 원인이 확정된 것입니다.
- 2같은 화면의 Status 가 404·500·302 인지 확인합니다.
- 3Headers 의
Content-Type이application/json인지 봅니다.text/html이면 서버가 처음부터 JSON 을 보내지 않은 것입니다.
코드에서 미리 막기
const res = await fetch("/api/users");
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const type = res.headers.get("content-type") ?? "";
if (!type.includes("application/json")) {
const body = await res.text();
throw new Error(`JSON 이 아닌 응답: ${body.slice(0, 80)}`);
}
const data = await res.json();이렇게 두면 콘솔에 Unexpected token '<' 대신 HTTP 404 처럼 진짜 원인이 찍힙니다. 상태 코드가 200 인데 HTML 이 온다면 로그인 리다이렉트나 프록시 설정을 먼저 의심하세요.
Unexpected end of JSON input — 읽을 내용이 없다
Chrome·Node : SyntaxError: Unexpected end of JSON input
Python : json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)파서가 값을 읽기 시작하기도 전에 입력이 끝났다는 뜻입니다. 빈 문자열을 파싱하면 나옵니다. 파이썬은 빈 문자열과 HTML 응답 모두 Expecting value: line 1 column 1 (char 0) 으로 똑같이 표시하니, 파이썬에서는 받은 내용을 직접 출력해 보는 것이 가장 빠릅니다.
- 서버가 본문 없이
204 No Content를 응답했는데res.json()을 호출 - 오류 상황에서 서버가 빈 본문을 보냄 (상태 코드를 같이 확인)
- 파일이 0바이트이거나, 저장 도중 프로그램이 종료돼 비어 있음
const res = await fetch("/api/items/3", { method: "DELETE" });
const text = await res.text();
const data = text ? JSON.parse(text) : null; // 본문이 비었으면 null끝이 잘린 JSON 은 메시지가 조금 다릅니다. {"name": "kim", "tags": ["a", "b" 처럼 중간에 끊기면 Chrome·Node.js 는 Expected ',' or ']' after array element in JSON at position 33 처럼 '다음에 올 기호가 없다'고 알려 줍니다. 표시된 위치가 입력의 맨 끝이라면 잘린 데이터를 의심하세요. 로그를 복사하다 끝부분이 빠지거나, 용량 제한으로 응답이 중간에 끊긴 경우가 흔합니다.
JavaScript 문법은 JSON 이 아니다 — 문법 실수 모음
JSON 은 JavaScript 객체 표기에서 출발했지만 훨씬 엄격합니다. 코드에서 객체를 쓰던 습관대로 JSON 파일을 손으로 고치면 아래 실수가 나옵니다.
| 실수 | 틀린 예 | Chrome·Node.js 메시지 | Python 메시지 |
|---|---|---|---|
| 객체 끝 쉼표 | {"a": 1,} | Expected double-quoted property name | Expecting property name enclosed in double quotes |
| 배열 끝 쉼표 | [1, 2, 3,] | Unexpected token ']' | Expecting value |
| 작은따옴표 | {'name': 'kim'} | Expected property name or '}' | Expecting property name enclosed in double quotes |
| 따옴표 없는 키 | {name: "kim"} | Expected property name or '}' | Expecting property name enclosed in double quotes |
| 쉼표 빠짐 | {"a": 1 "b": 2} | Expected ',' or '}' after property value | Expecting ',' delimiter |
| 주석 | {"a": 1 // 메모} | Expected ',' or '}' after property value | Expecting ',' delimiter |
눈여겨볼 점은 같은 실수라도 메시지가 실수 자체를 말해 주지 않는다는 것입니다. 주석을 넣어도 '주석은 안 된다'가 아니라 '쉼표나 } 가 와야 한다'고 나옵니다. 메시지 문구보다 위치를 믿고, 그 자리 앞뒤에 위 표의 실수가 있는지 확인하는 편이 빠릅니다.
tsconfig.json 과 VS Code 의 settings.json 은 주석을 허용하는 JSONC 형식이라 주석이 있어도 동작합니다. 여기에 익숙해진 채로 package.json 이나 API 로 주고받는 JSON 에 주석을 넣으면 오류가 납니다.
값이 JSON 에 없는 형식일 때 — NaN, True, None, 007
JSON 이 허용하는 값은 문자열, 숫자, true·false, null, 배열, 객체뿐입니다. 다른 언어의 값 표기를 그대로 옮기면 오류가 납니다.
| 값 | Chrome·Node.js 메시지 | 고치는 법 |
|---|---|---|
NaN, Infinity | Unexpected token 'N' / 'I' | null 이나 문자열로 바꿔 저장 |
True, None (파이썬 표기) | Unexpected token 'T' / 'N' | json.dumps() 로 만들면 true·null 로 바뀜 |
undefined | Unexpected token 'u' | null 사용. JSON.stringify 는 undefined 속성을 아예 뺍니다 |
007 (앞자리 0) | Unexpected number in JSON at position 7 | 숫자 7 또는 문자열 "007" |
0x1F (16진수) | Expected ',' or '}' after property value | 10진수 31 로 저장 |
Unexpected token 'N' 은 NaN 과 None 모두에서 나옵니다. 메시지 뒤에 따라오는 따옴표 속 조각("{"score": NaN}" is not valid JSON)이 문제 부근의 원문이니 그 부분을 보고 어느 쪽인지 구분하세요.
파이썬이 만든 JSON 이 JavaScript 에서 깨지는 경우
파이썬 json 모듈은 기본 설정에서 NaN 과 Infinity 를 그대로 써 넣고, 읽을 때도 받아 줍니다. 그래서 파이썬끼리 주고받을 때는 멀쩡하다가 브라우저나 Node.js 가 읽는 순간 오류가 납니다.
import json
json.dumps({"score": float("nan")})
# '{"score": NaN}' ← 표준 JSON 이 아님
json.dumps({"score": float("nan")}, allow_nan=False)
# ValueError: Out of range float values are not JSON compliantallow_nan=False 를 주면 저장하는 순간 오류가 나므로, 잘못된 값이 밖으로 나가기 전에 잡을 수 있습니다. 비슷한 실수로 str(data) 나 print(data) 로 만든 문자열은 {'ok': True, 'v': None} 처럼 작은따옴표와 True·None 이 들어가 JSON 이 아닙니다. JSON 이 필요하면 반드시 json.dumps() 를 쓰세요.
문자열 안에서 나는 오류 — 줄바꿈, 역슬래시, BOM
Bad control character in string literal
Chrome·Node : Bad control character in string literal in JSON at position 14 (line 1 column 15)
Python : Invalid control character at: line 1 column 15 (char 14)문자열 따옴표 안에 실제 줄바꿈이나 탭이 들어가면 나옵니다. 여러 줄 텍스트를 JSON 문자열에 넣을 때는 줄바꿈을 \n 두 글자로 써야 합니다. 손으로 이어 붙이지 말고 JSON.stringify()·json.dumps() 로 만들면 자동으로 바뀝니다.
Bad escaped character — 윈도우 경로
Chrome·Node : Bad escaped character in JSON at position 13 (line 1 column 14)
Python : Invalid \escape: line 1 column 13 (char 12){"path": "C:\Users\kim"} 처럼 역슬래시를 하나만 쓰면, 파서가 \U 를 이스케이프 문자로 읽으려다 실패합니다. 역슬래시를 두 번(C:\\Users\\kim) 쓰거나 슬래시(C:/Users/kim)로 바꾸세요. 파이썬과 Node.js 의 파일 함수는 윈도우에서도 슬래시 경로를 그대로 받아들입니다. 두 메시지의 위치가 1 차이 나는 것도 볼 만합니다. 파이썬은 역슬래시를, Chrome·Node.js 는 그다음 글자 U 를 가리킵니다.
눈에 안 보이는 BOM
Chrome·Node : Unexpected token '', "{"a": 1}" is not valid JSON
Python : Unexpected UTF-8 BOM (decode using utf-8-sig): line 1 column 1 (char 0)파일 내용은 멀쩡한데 첫 글자에서 오류가 난다면 BOM(Byte Order Mark)을 의심하세요. 파일 맨 앞에 붙는 보이지 않는 표식이라, Chrome·Node.js 메시지에서 Unexpected token '' 처럼 따옴표 안이 비어 보이는 것이 특징입니다. 윈도우에서는 PowerShell 5.1 이 흔한 출처입니다. Set-Content -Encoding UTF8 로 저장하면 BOM 이 붙고, > 나 Out-File 로 저장하면 아예 UTF-16 파일이 되어 Node.js 에서 Unexpected token '�' 가 납니다.
- 파이썬:
open(path, encoding="utf-8-sig")로 열면 BOM 을 건너뛰고 읽습니다. - Node.js:
JSON.parse(text.replace(/^\uFEFF/, ""))로 첫 글자의 BOM 을 지웁니다. - VS Code: 오른쪽 아래 인코딩 표시(
UTF-8 with BOM)를 누르고 Save with Encoding →UTF-8로 다시 저장합니다. - PowerShell 7 은 BOM 없는 UTF-8 이 기본이라 이 문제가 없습니다.
"undefined" is not valid JSON — 문자열이 아닌 값을 넣었다
JSON.parse 는 문자열을 받습니다. 다른 값을 넣으면 먼저 문자열로 바뀐 뒤 파싱되기 때문에, 메시지에 JSON 이 아니라 그 값의 문자열 모양이 찍힙니다.
| 메시지 | 무엇을 넣었나 | 흔한 상황 |
|---|---|---|
"undefined" is not valid JSON | undefined 또는 문자열 "undefined" | localStorage.setItem("user", undefined) 로 저장된 값을 다시 읽음 |
"[object Object]" is not valid JSON | 이미 객체인 값 | axios 의 response.data 나 res.json() 결과처럼 이미 파싱된 값을 또 JSON.parse |
Unexpected non-whitespace character after JSON | JSON 두 개가 이어진 문자열 | 한 줄에 JSON 하나씩 쓴 JSON Lines(.jsonl) 파일을 통째로 파싱 |
// 저장: undefined 대신 null 이 들어가게
localStorage.setItem("user", JSON.stringify(user ?? null));
// 읽기: 저장된 적 없으면 getItem 이 null 을 준다
const raw = localStorage.getItem("user");
const saved = raw ? JSON.parse(raw) : null;
// JSON Lines: 줄마다 따로 파싱
const rows = text.split("\n").filter(Boolean).map((line) => JSON.parse(line));localStorage 는 값을 무조건 문자열로 바꿔 저장하므로 undefined 를 넣으면 "undefined" 라는 9글자 문자열이 남습니다. 이미 잘못 저장된 값이 있다면 개발자 도구 Application 탭에서 해당 키를 지우세요. axios 는 JSON 응답을 알아서 객체로 바꿔 주기 때문에 response.data 를 한 번 더 파싱할 필요가 없습니다.
빠르게 찾는 순서
- 1메시지에
<가 보이면 JSON 이 아니라 응답을 확인합니다 (Network 탭). - 2
end of JSON input이면 입력이 비었는지, 끝이 잘렸는지 봅니다. - 3위치(
position,line·column)가 있으면 그 자리로 가서 바로 앞 글자를 봅니다. - 4위치가 없거나 파일이 크면 JSON 포매터에 붙여넣어 오류 줄을 확인합니다. 명령줄에서는
python -m json.tool 파일.json이line 4 column 1처럼 위치를 알려 줍니다. - 5JSON 을 코드에서 만든다면 문자열 이어 붙이기를 그만두고
JSON.stringify()·json.dumps()를 씁니다. 이 글의 문법 오류는 대부분 손으로 만든 JSON 에서 생깁니다.
JSON 포매터는 붙여넣은 내용을 서버로 보내지 않고 브라우저 안에서만 검사합니다. 회사 데이터처럼 외부로 보내기 곤란한 JSON 도 위치를 확인할 수 있습니다.
자주 묻는 질문
Q. JSON 에 주석을 넣을 방법은 없나요?
표준 JSON 에는 없습니다. 설명이 꼭 필요하면 "_comment": "..." 같은 키를 두는 방법을 많이 씁니다. 설정 파일이라면 읽는 프로그램이 JSONC 나 JSON5 를 지원하는지 확인하세요. JSON.parse 와 파이썬 json 모듈은 둘 다 주석을 받지 않습니다.
Q. position 숫자와 에디터의 칸 번호가 1 차이 납니다.
position(파이썬은 char)은 0부터, 에디터의 칸(column)은 1부터 세기 때문입니다. 최신 Chrome·Node.js 는 (line 1 column 27) 처럼 줄·칸을 함께 보여 주니 그 값을 에디터의 줄 이동(VS Code 는 Ctrl+G)에 그대로 쓰면 됩니다.
Q. 파이썬에서는 읽히는데 JavaScript 에서만 오류가 납니다.
NaN·Infinity 가 들어 있을 가능성이 큽니다. 파이썬 json 모듈은 이 값들을 기본으로 허용하지만 표준 JSON 과 JavaScript 는 허용하지 않습니다. 저장하는 쪽에서 allow_nan=False 로 걸러 내고, 값이 없으면 None 으로 저장하세요.
Q. 한글이 `\ud55c\uae00` 처럼 저장됩니다. 깨진 건가요?
깨진 것이 아닙니다. 파이썬 json.dumps 는 기본으로 ASCII 밖의 글자를 \u 이스케이프로 바꿔 씁니다. 읽으면 다시 한글로 돌아오므로 오류는 아닙니다. 파일에서도 한글 그대로 보고 싶다면 json.dumps(data, ensure_ascii=False) 를 쓰세요.
Q. res.json() 대신 JSON.parse(await res.text()) 를 쓰면 뭐가 다른가요?
파싱 결과는 같습니다. 다만 텍스트로 먼저 받아 두면 파싱에 실패했을 때 실제로 받은 내용을 로그로 남길 수 있어 원인 찾기가 훨씬 쉬워집니다. 운영 코드에서는 실패했을 때 앞부분 100자 정도만 기록해 두는 방식을 많이 씁니다.
붙여넣은 JSON 을 정렬하고, 오류가 있으면 메시지와 줄·칸 위치를 짚어 해당 줄을 강조합니다. 브라우저 안에서만 처리해 데이터가 밖으로 나가지 않습니다.