← 블로그 목록
웹 개발작성 · 10분 읽기dev.to 영문판

JSON 파싱 오류 해결 — Unexpected token 메시지별 원인과 조치

JSON 오류 메시지는 짧은 영어 한 줄이라 막막해 보이지만, 실제로 마주치는 원인은 몇 가지로 정해져 있습니다. 서버가 JSON 대신 HTML 을 돌려준 경우, 읽을 내용이 비어 있는 경우, 그리고 JavaScript 코드 쓰듯 JSON 을 손으로 쓴 경우가 대부분입니다. Chrome·Node.js·Python 에서 실제로 출력되는 메시지를 그대로 옮기고, 메시지마다 무엇을 확인하면 되는지 정리했습니다.

이 글에서 소개하는 무료 도구
JSON 포매터·검증기

붙여넣은 JSON 을 정렬하고, 오류가 있으면 메시지와 줄·칸 위치를 짚어 해당 줄을 강조합니다. 브라우저 안에서만 처리해 데이터가 밖으로 나가지 않습니다.

JSON 오류 위치 찾기

오류 메시지 읽는 법

같은 JSON 이라도 어디서 읽느냐에 따라 메시지 문구가 다릅니다. 아래는 끝에 쉼표가 남은 {"name": "kim", "age": 20,} 를 환경별로 읽었을 때 실제로 나온 메시지입니다.

환경메시지
Chrome·Edge·Node.jsExpected double-quoted property name in JSON at position 26 (line 1 column 27)
Python jsonExpecting property name enclosed in double quotes: line 1 column 27 (char 26)
예전 버전 Chrome·Node.jsUnexpected token } in JSON at position 26

숫자를 읽는 규칙은 두 가지입니다. position 과 파이썬의 char0부터 센 글자 위치이고, line·column 은 에디터처럼 1부터 셉니다. 그래서 position 26 과 column 27 은 같은 자리를 가리킵니다.

파서는 쉼표 뒤에 키가 올 거라 기대하다가 26번 자리에서 `}` 를 만나 멈춥니다. 고칠 곳은 그 바로 앞의 쉼표입니다.
💡

메시지가 가리키는 곳은 '파서가 더는 읽을 수 없게 된 자리'입니다. 실수는 보통 그보다 앞에 있으니, 표시된 위치에서 왼쪽으로 거슬러 올라가며 보세요.

Unexpected token '<' — 서버가 JSON 대신 HTML 을 보냈다

fetch 로 API 를 부르고 res.json() 을 했을 때 가장 자주 보는 오류입니다. 브라우저마다 문구는 달라도 뜻은 같습니다.

text
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 을 응답
응답을 확인하지 않고 바로 `res.json()` 을 부르면 진짜 원인(404·500)이 JSON 오류 뒤에 숨습니다.

확인 방법

  1. 1개발자 도구(F12) → Network 탭 → 해당 요청을 클릭하고 Response 를 봅니다. HTML 이 보이면 원인이 확정된 것입니다.
  2. 2같은 화면의 Status 가 404·500·302 인지 확인합니다.
  3. 3Headers 의 Content-Typeapplication/json 인지 봅니다. text/html 이면 서버가 처음부터 JSON 을 보내지 않은 것입니다.

코드에서 미리 막기

javascript
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 — 읽을 내용이 없다

text
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바이트이거나, 저장 도중 프로그램이 종료돼 비어 있음
javascript
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 파일을 손으로 고치면 아래 실수가 나옵니다.

왼쪽은 JavaScript 코드로는 문제없이 동작하지만 JSON 으로는 표시한 줄이 모두 오류입니다. 오른쪽이 같은 내용의 올바른 JSON 입니다.
실수틀린 예Chrome·Node.js 메시지Python 메시지
객체 끝 쉼표{"a": 1,}Expected double-quoted property nameExpecting 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 valueExpecting ',' delimiter
주석{"a": 1 // 메모}Expected ',' or '}' after property valueExpecting ',' 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, InfinityUnexpected token 'N' / 'I'null 이나 문자열로 바꿔 저장
True, None (파이썬 표기)Unexpected token 'T' / 'N'json.dumps() 로 만들면 true·null 로 바뀜
undefinedUnexpected token 'u'null 사용. JSON.stringify 는 undefined 속성을 아예 뺍니다
007 (앞자리 0)Unexpected number in JSON at position 7숫자 7 또는 문자열 "007"
0x1F (16진수)Expected ',' or '}' after property value10진수 31 로 저장

Unexpected token 'N'NaNNone 모두에서 나옵니다. 메시지 뒤에 따라오는 따옴표 속 조각("{"score": NaN}" is not valid JSON)이 문제 부근의 원문이니 그 부분을 보고 어느 쪽인지 구분하세요.

파이썬이 만든 JSON 이 JavaScript 에서 깨지는 경우

파이썬 json 모듈은 기본 설정에서 NaNInfinity 를 그대로 써 넣고, 읽을 때도 받아 줍니다. 그래서 파이썬끼리 주고받을 때는 멀쩡하다가 브라우저나 Node.js 가 읽는 순간 오류가 납니다.

python
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 compliant

allow_nan=False 를 주면 저장하는 순간 오류가 나므로, 잘못된 값이 밖으로 나가기 전에 잡을 수 있습니다. 비슷한 실수로 str(data)print(data) 로 만든 문자열은 {'ok': True, 'v': None} 처럼 작은따옴표와 True·None 이 들어가 JSON 이 아닙니다. JSON 이 필요하면 반드시 json.dumps() 를 쓰세요.

문자열 안에서 나는 오류 — 줄바꿈, 역슬래시, BOM

Bad control character in string literal

text
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 — 윈도우 경로

text
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

text
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 JSONundefined 또는 문자열 "undefined"localStorage.setItem("user", undefined) 로 저장된 값을 다시 읽음
"[object Object]" is not valid JSON이미 객체인 값axios 의 response.datares.json() 결과처럼 이미 파싱된 값을 또 JSON.parse
Unexpected non-whitespace character after JSONJSON 두 개가 이어진 문자열한 줄에 JSON 하나씩 쓴 JSON Lines(.jsonl) 파일을 통째로 파싱
javascript
// 저장: 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. 1메시지에 < 가 보이면 JSON 이 아니라 응답을 확인합니다 (Network 탭).
  2. 2end of JSON input 이면 입력이 비었는지, 끝이 잘렸는지 봅니다.
  3. 3위치(position, line·column)가 있으면 그 자리로 가서 바로 앞 글자를 봅니다.
  4. 4위치가 없거나 파일이 크면 JSON 포매터에 붙여넣어 오류 줄을 확인합니다. 명령줄에서는 python -m json.tool 파일.jsonline 4 column 1 처럼 위치를 알려 줍니다.
  5. 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 포매터·검증기

붙여넣은 JSON 을 정렬하고, 오류가 있으면 메시지와 줄·칸 위치를 짚어 해당 줄을 강조합니다. 브라우저 안에서만 처리해 데이터가 밖으로 나가지 않습니다.

JSON 오류 위치 찾기

다른 글