
Python의 JSONDecodeError로 파손 지점 특정하기──line/column/char 읽는 법
요약
Python의 JSONDecodeError가 제공하는 line, column, char 정보를 활용하여 JSON 데이터의 파손 지점을 정확히 찾아내는 방법을 설명합니다. 에러 메시지의 좌표를 읽는 법부터 파손된 행을 시각적으로 표시하는 헬퍼 함수 작성법까지 다룹니다.
핵심 포인트
- JSONDecodeError는 에러 발생 위치의 행, 열, 문자 오프셋 정보를 포함함
- trailing comma(끝에 붙은 콤마)는 표준 JSON에서 흔한 문법 오류 원인임
- e.lineno, e.colno, e.pos 속성을 사용하여 에러 위치를 핀포인트로 특정 가능함
- 에러 메시지의 좌표를 활용해 파손된 행에 캐럿(^)을 표시하는 디버깅 도구 구현 가능
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()
...
$ python load.py
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()
...
$ python pinpoint.py
에러: 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"],
$ python load.py
8080
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

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기