json.decoder.JSONDecodeError: Expecting value: line 4 column 32 (char 72)
설정 파일이나 API 응답을 json.loads()에 전달하는 순간 나타나는 이 한 줄. "어딘가 잘못되었다"는 것은 알겠지만, 거대한 JSON의 어디가 잘못되었는지는 알 수 없습니다. 하지만 사실, 이 에러는 파손된 위치를 좌표로 반드시 알려주고 있습니다.
이 기사에서는 끝에 콤마(trailing comma)가 붙은 config.json을 최소 단위로 재현하여, "메시지의 좌표 읽기" → "파손된 행을 핀포인트로 표시하기" → "원인 패턴으로 추측하기" → "수정 후 확인" 순서로 진행합니다. 게재된 코드와 출력은 Python 3.11에서 실행하여 확인을 마쳤습니다.
결론: JSONDecodeError는 파손 위치를 "좌표"로 전달한다
JSONDecodeError의 메시지는 **무엇을 기대했는지(msg) + 어디에서 막혔는지(line / column / char)**의 2종 세트입니다. 범인을 찾기 전에 이 좌표를 읽으면 위치는 거의 확정됩니다.
1. 좌표 읽기 … line(행) / column(열) / char(0부터 시작하는 오프셋)
2. 파손된 행 출력 … 예외의 e.pos 등을 사용하여 해당 행에 캐럿(caret) 표시
3. 원인 추측 … 끝에 붙은 콤마 / 싱글 쿼트 / 불필요한 데이터 … 유형별로 암기
순서대로 실제 동작하는 예시를 통해 살펴보겠습니다.
1. 최소 재현: 끝에 콤마가 있는 config.json
가장 빈번한 사고는 배열(array)이나 객체(object) 끝의 콤마입니다. JavaScript에서는 허용되기도 하지만, 표준 JSON에서는 문법 위반입니다.
{
"host": "localhost",
"port": 8080,
...
import json
with open("config.json", encoding="utf-8") as f:
text = f.read()
...
Traceback (most recent call last):
File ".../load.py", line 6, in <module>
config = json.loads(text)
...
2. 좌표 읽기: line / column / char
마지막 한 줄에 원인 특정에 필요한 정보가 모두 들어 있습니다.
Expecting value : 파서(parser)가 "다음에는 값이 와야 한다"고 생각했는데 오지 않음
line 4 : 4행 (1부터 시작)
column 32 : 해당 행의 32번째 문자 (1부터 시작)
...
4행은 "features": ["auth", "cache",], 입니다. 끝에 붙은 콤마 직후의 ] 위치에서 "다음 값이 와야 하는데" 닫는 괄호가 왔기 때문에, Expecting value(값을 기대함) 단계에서 멈춘 것입니다.
char는 문자열 시작점으로부터의 0부터 시작하는 오프셋이므로, text[72]를 통해 해당 문자를 직접 추출할 수 있습니다.
3. 파손된 행을 핀포인트로 표시하기
좌표를 매번 눈으로 세는 것은 번거롭습니다. JSONDecodeError는 속성(msg / pos / lineno / colno)을 가지고 있으므로, 파손된 행 아래에 캐럿을 표시하는 작은 헬퍼(helper)를 한 번 작성해 두면 재사용할 수 있습니다.
import json
with open("config.json", encoding="utf-8") as f:
text = f.read()
...
에러: Expecting value
위치 : 4행 32열 (char 72)
"features": ["auth", "cache",],
...
splitlines()[e.lineno - 1]로 해당 행을 가져오고, colno - 1개의 공백으로 캐럿을 옮기기만 하면 됩니다. 거대한 JSON이라도 파손 지점을 한눈에 확인할 수 있게 됩니다. API 응답처럼 행수가 많은 JSON일수록 효과적입니다.
4. 원인을 "유형"으로 추측하는 빠른 참조표
JSONDecodeError의 msg
는 원인에 따라 거의 정해져 있습니다. 대표적인 것들을 실제 메시지로 나열합니다 (모두 Python 3.11.15의 실제 출력).
| 자주 발생하는 오류 형태 | 예시 | 실제 msg (앞부분) |
|---|
| 마지막 쉼표 (Trailing comma) | ["a", "b",] | Expecting value |
| 작은따옴표 (Single quote) | {'name': 1} | Expecting property name enclosed in double quotes |
| 값이나 객체가 두 개 연속됨 | {"a":1}{"b":2} | Extra data |
| 빈 문자열·빈 응답 | "" | Expecting value (char 0) |
$ python -c "import json; json.loads("{'name': 1}")" # 작은따옴표
$ python -c 'import json; json.loads("{\"a\":1}{\"b\":2}")' # 여분의 데이터
$ python -c 'import json; json.loads("")' # 빈 문자열
JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
JSONDecodeError: Extra data: line 1 column 8 (char 7)
JSONDecodeError: Expecting value: line 1 column 1 (char 0)
포인트는 두 가지입니다. Expecting property name enclosed in double quotes는 "키가 큰따옴표로 둘러싸여 있지 않음"(작은따옴표나 따옴표가 없는 키)을 의미합니다.
**Extra data**는 "유효한 JSON 뒤에 쓰레기 데이터가 이어짐"을 의미하며, json.loads에 전달한 응답에 로그가 섞여 있는 경우 등에 발생합니다. 빈 응답은 char 0에서 Expecting value가 되므로, **"애초에 내용이 비어 있는지"**를 판별하는 데도 사용할 수 있습니다.
5. 수정하고 확인하기
원인(마지막 쉼표)을 알았으므로, ] 앞의 쉼표를 삭제합니다.
- "features": ["auth", "cache",],
+ "features": ["auth", "cache"],
config["port"]의 8080이 출력되었으며, 파싱(Parsing)에 성공했습니다. **"좌표로 위치를 특정 → 유형으로 원인을 파악 → 최소한의 diff로 수정"**이 JSON 파싱 에러를 해결하는 가장 빠른 경로입니다.
JSONDecodeError의 속성 (보충)
표준 라이브러리의 json.JSONDecodeError는 ValueError의 서브클래스(Subclass)이며, 다음 속성을 가집니다 (Python 공식 문서).
msg … 에러 설명 (Expecting value 등)
doc … 파싱하려 했던 원래 문자열 전체
pos … 실패 위치의 0부터 시작하는 오프셋 (doc[pos]로 해당 문자를 가져올 수 있음)
lineno … 실패한 행 (1부터 시작)
colno … 실패한 열 (1부터 시작)
pos와 lineno/colno의 기준점이 다르다는 점만 주의하면, 로그 정렬이나 자동 재시도(Retry) 분기 처리에 사용할 수 있습니다.
요약
JSONDecodeError는 반드시 msg(무엇을 기대했는지) + line / column / char(어디서 막혔는지)를 반환합니다.
char (즉, e.pos)는 0부터 시작하고, line/column은 1부터 시작합니다. 슬라이싱(Slicing)에는 e.pos를 사용하세요.
e.lineno/e.colno를 사용하여 오류가 발생한 행에 캐럿(Caret)을 표시하는 헬퍼(Helper) 함수를 하나 만들어 두면, 거대한 JSON이라도 한 번에 찾아낼 수 있습니다.
msg
는 원인의 유형과 대응 관계입니다: Trailing comma (末尾カンマ) → Expecting value / Single quote (シングルクォート) → property name … double quotes / Extra data (余分なデータ) → Extra data - 게재된 코드와 출력은 Python 3.11에서 실행 확인 완료되었습니다.
도움이 되었다면 ❤️ 와 Zenn 팔로우로 응원해 주세요. 다음 디버깅 소재를 작성하는 데 큰 힘이 됩니다.
Discussion