JSON은 규칙을 알기 전까지는 쓸데없이 까다롭게 느껴질 만큼 엄격합니다. 마지막 쉼표는 안 됩니다. 큰따옴표만 씁니다. 주석은 없습니다. 키는 반드시 따옴표로 감싸야 합니다. 이 중 하나라도 어기면 문서 전체가 거부되고, 문자 위치만 알려 줄 뿐 무엇이 잘못됐는지는 거의 말해 주지 않는 메시지가 나옵니다.
이 글은 오류 메시지를 원인에, 원인을 해결 방법에 연결해 설명합니다.
빠른 답
- 위치를 찾습니다. 모든 파서는 줄과 열, 또는 문자 오프셋을 알려 줍니다. 그곳으로 가세요. 실수는 그 지점이나 바로 앞에 있습니다.
- 흔한 다섯 가지를 확인합니다. 마지막 쉼표, 작은따옴표, 따옴표 없는 키, 주석, 항목 사이의 빠진 쉼표입니다.
- 오류가 위치 0 또는 1행 1열이라면 JSON 자체가 문제가 아닙니다. HTML 오류 페이지, 빈 응답, 바이트 순서 표시(BOM) 같은 다른 것을 받은 것입니다.
어떤 컴퓨터에서든 정확한 위치를 확인하는 방법입니다.
python3 -m json.tool broken.json
jq . broken.json
오류 메시지 읽는 법
같은 실수라도 언어마다 메시지가 다릅니다. 자신의 메시지를 찾고, 원인을 확인하세요.
| 메시지(축약) | 나타나는 곳 | 흔한 원인 |
|---|---|---|
Unexpected token '<', "<!DOCTYPE"... is not valid JSON | JavaScript | JSON이 아니라 HTML 페이지를 받았습니다. 아래 참고 |
Unexpected end of JSON input | JavaScript | 빈 응답이거나 파일이 잘렸습니다 |
Expected double-quoted property name | JavaScript | } 앞의 마지막 쉼표, 또는 따옴표가 없거나 작은따옴표인 키 |
Unexpected non-whitespace character after JSON | JavaScript | 문서 두 개가 이어 붙었거나, 닫는 중괄호 뒤에 불필요한 내용이 있습니다 |
Bad control character in string literal | JavaScript | 문자열 안에 탭이나 줄바꿈이 그대로 들어 있습니다 |
Expecting value: line 1 column 1 (char 0) | Python | 빈 입력이거나 JSON이 아닙니다 |
Expecting property name enclosed in double quotes | Python | 마지막 쉼표, 작은따옴표, 또는 따옴표 없는 키 |
Expecting ',' delimiter | Python | 두 항목 또는 멤버 사이에 쉼표가 빠졌습니다 |
Extra data | Python | 파일에 문서가 둘 이상 있습니다(흔히 NDJSON) |
Invalid control character | Python | 문자열 안에 탭이나 줄바꿈이 그대로 들어 있습니다 |
Unexpected UTF-8 BOM | Python | 파일 맨 앞에 보이지 않는 바이트 순서 표시가 있습니다 |
원인을 하나씩 살펴보기
마지막 쉼표
{ "name": "Ada", "role": "admin", }
JavaScript 코드에서는 허용되지만 JSON에서는 금지입니다. 파서는 다음 키를 기대하다가 }를 만납니다. 마지막 쉼표를 지우세요. 손으로 편집한 설정 파일과 문자열 연결로 JSON을 만드는 코드에서 흔히 생깁니다.
작은따옴표
{ 'name': 'Ada' }
JSON 문자열은 항상 큰따옴표를 씁니다. 대개 Python dict를 json.dumps()가 아니라 print()로 출력해서 생깁니다. 진짜 해결책은 그것을 만든 코드에 있습니다. 문자열 포매팅이 아니라 JSON 라이브러리로 직렬화하세요.
따옴표 없는 키
{ name: "Ada" }
JavaScript로는 올바르지만 JSON으로는 잘못입니다. 모든 키를 따옴표로 감싸세요.
주석
{ "port": 8080 // default }
JSON에는 주석이 없습니다. 하지만 "고치기" 전에 주의하세요. 일부 파일은 주석이 있어도 되는 형식입니다. tsconfig.json, VS Code의 settings.json 등 많은 설정 파일은 JSONC(주석을 허용하는 JSON)나 JSON5이며, 이를 읽는 도구는 주석을 받아들입니다. 그런 파일이라면 문제는 파일이 아니라, 그것을 넘겨주는 엄격한 파서에 있습니다.
빠진 쉼표
{ "a": 1 "b": 2 }
파서는 실수보다 뒤에 있는 "b"에서 오류를 보고합니다. 오류가 멀쩡한 키를 가리키면, 그 앞 줄의 끝을 살펴보세요.
JSON에 없는 값
NaN, Infinity, undefined, 그리고 007처럼 0으로 시작하는 숫자는 모두 잘못입니다. 16진수 0xFF도 마찬가지입니다. 읽는 쪽이 기대하는 바에 따라 null, 문자열, 일반 10진수를 쓰세요.
문자열 안의 줄바꿈과 탭
문자열 안의 줄바꿈은 \n, 탭은 \t로 이스케이프해야 합니다. 따옴표 안의 실제 줄바꿈은 잘못입니다. 여러 줄 값을 JSON 파일에 손으로 붙여 넣을 때 자주 생깁니다.
백슬래시
"C:\new\folder" 같은 Windows 경로는 \n과 \f가 이스케이프 시퀀스라서 깨집니다. 백슬래시를 모두 두 번씩 쓰거나("C:\\new\\folder") 슬래시를 쓰세요.
둥근 따옴표
“name”은 문자열처럼 보이지만 아닙니다. 이것은 "가 아니라 타이포그래피용 따옴표입니다. 문서, 메신저, 슬라이드에서 JSON을 복사했거나, iPhone·iPad에서 입력할 때 스마트 문장 부호가 곧은 따옴표를 바꿔 버려서 생깁니다. iOS에서 JSON을 편집하기 전에 설정 → 일반 → 키보드에서 끄세요.
"위치 0" 오류: JSON이 아닙니다
오류가 맨 첫 글자에 있다면 문법이 아니라 내용이 잘못된 것입니다.
- 위치 0의
<는 HTML을 파싱했다는 뜻입니다. 서버가 오류 페이지, 로그인 페이지, 404를 돌려줬고, 코드가 그것을 JSON으로 읽으려 했습니다. 파싱하기 전에 원본 응답과 상태 코드를 로그로 남기세요. - 빈 입력 은 응답에 본문이 없다는 뜻입니다. 주로
204 No Content이거나, 서버가 응답하기 전에 실패한 요청입니다. - 맨 앞의 보이지 않는 문자 는 대개 Windows 편집기가 붙인 UTF-8 바이트 순서 표시입니다. Python에서는
encoding="utf-8-sig"로 파일을 열고, 다른 환경에서는 BOM 없는 UTF-8로 다시 저장하세요.
NDJSON은 깨진 JSON이 아닙니다
한 줄에 JSON 객체 하나씩 담은 파일(.jsonl, .ndjson, 로그 출력)은 두 번째 줄 시작 부분에서 "Extra data"나 "Unexpected non-whitespace"로 실패합니다. 각 줄은 그 자체로 올바릅니다. 줄 단위로 파싱하거나, 이 형식을 아는 도구를 쓰세요. jq -c . file.jsonl은 모든 줄을 읽습니다.
자동으로 복구하기
흔한 실수(마지막 쉼표, 작은따옴표, 따옴표 없는 키, 주석)는 타당한 해석이 하나뿐이어서 자동 복구가 안전합니다. 이를 해 주는 도구는 다음과 같습니다.
jsonrepair: JavaScript 라이브러리이자 명령줄 도구입니다.- 대부분의 코드 편집기: JSON5 또는 "fix JSON" 확장 프로그램을 통해 사용합니다.
- 입력하는 동안 검증해 주는 JSON 편집기 앱.
잘린 파일에는 자동 복구가 안전하지 않습니다. 복구 도구는 열린 괄호를 닫아서, 데이터가 빠진 올바른 문서를 돌려줍니다. 그 대신 다시 가져오거나 다시 내보내세요.
iPhone과 iPad에서 Smart Json은 오류를 줄과 열과 함께 쉬운 말로 보여 줍니다. Go는 커서를 그 위치로 옮기고, Fix는 주석, 마지막 쉼표, 작은따옴표, 따옴표 없는 키를 한 번에 복구합니다.

JSON 오류를 줄과 열과 함께 쉬운 말로 알려 주고, 그 위치로 이동하며, 주석, 마지막 쉼표, 작은따옴표, 따옴표 없는 키를 한 번의 탭으로 복구합니다.
기능애초에 깨지지 않게 하기
잘못된 JSON 파일 대부분은 손으로 썼거나 문자열 연결로 조립한 것입니다. JSON은 직렬화기(json.dumps, JSON.stringify, JSONEncoder)로 생성하고, 손으로 편집한 파일은 커밋하기 전에 검증하세요. pre-commit 훅에서 jq . file.json > /dev/null을 실행하면 위의 문제를 다른 사람이 보기 전에 모두 잡아냅니다.