파이썬 JSONDecodeError 해결: 작은따옴표·마지막 쉼표 원인 구분법

json.loads()나 json.load()에서 JSONDecodeError가 발생하면 먼저 오류 메시지의 줄·열 위치와 실제 입력을 확인해야 합니다. 작은따옴표, 따옴표 없는 키, 마지막 쉼표처럼 Python 표현과 JSON 문법이 다른 경우가 흔하며, Python dict를 문자열로 만든 것이라면 문법을 손으로 고치기보다 json.dumps()로 다시 직렬화하는 편이 안전합니다.

오류 위치부터 확인하기

JSONDecodeError에는 메시지와 함께 lineno, colno, pos 같은 위치 정보가 있습니다. 예외를 그대로 출력하는 데서 멈추지 말고 입력의 해당 부분을 보세요.

import json

raw = "{'name': 'Kim',}"

try:
    data = json.loads(raw)
except json.JSONDecodeError as exc:
    print(exc.msg)
    print(exc.lineno, exc.colno, exc.pos)
    print(repr(raw))

repr()은 문자열 안의 따옴표나 이스케이프를 확인하는 데 도움이 됩니다. 로그에서 눈에 잘 보이지 않는 문자를 먼저 드러내면 오류 위치를 잘못 짚는 일을 줄일 수 있습니다.

작은따옴표와 따옴표 없는 키

JSON 객체의 이름과 문자열은 큰따옴표로 감싸야 합니다. 다음 입력은 Python dict처럼 보이지만 표준 JSON이 아닙니다.

{'name': 'Kim'}
{name: "Kim"}

JSON으로 직접 작성한다면 다음처럼 고칩니다.

{"name": "Kim"}

반대로 입력의 출처가 Python dict라면 문자열 치환으로 작은따옴표를 일괄 변경하지 않는 것이 좋습니다. 값 안에 따옴표가 있을 수 있기 때문입니다. 객체를 그대로 가지고 있다면 json.dumps()를 사용하세요.

import json

payload = {"name": "Kim", "items": ["book", "pen"]}
raw_json = json.dumps(payload)
data = json.loads(raw_json)

Python 공식 문서의 json 모듈 설명은 객체를 JSON 문자열로 직렬화하는 dumps()와 문자열을 역직렬화하는 loads()를 구분합니다. str(dict)의 결과를 JSON으로 간주하지 않는 것이 핵심입니다.

마지막 쉼표와 주석은 JSON 문법이 아니다

객체나 배열의 마지막 항목 뒤에 쉼표를 두는 습관은 Python 코드에서 익숙할 수 있지만 JSON 문법에는 마지막 멤버 뒤의 추가 쉼표가 정의되어 있지 않습니다.

{"name": "Kim",}
["book", "pen",]

또한 JSON 값과 구조 문법에는 주석 토큰이 포함되지 않습니다. 설정 파일을 JSON으로 읽는다면 주석을 제거하고, 마지막 쉼표를 앞 항목과 구분하는 쉼표로 착각하지 않았는지 확인하세요. 문법의 근거는 RFC 8259에서 확인할 수 있습니다.

여러 JSON 문서가 한 파일에 붙은 경우

파일에 JSON 객체를 차례로 기록했다고 해서 하나의 JSON 문서가 되지는 않습니다. JSON 문서는 하나의 직렬화된 값으로 구성되므로 다음처럼 객체가 이어지면 파싱이 실패합니다.

{"id": 1}
{"id": 2}

반복해서 dump()한 결과를 한 파일에 붙이는 방식이라면 저장 형식을 먼저 정해야 합니다. 여러 항목이면 하나의 배열로 묶거나, 애초에 줄 단위 형식이 필요한지 별도로 결정한 뒤 그 형식에 맞는 파서를 사용해야 합니다.

파일 전체를 명령줄에서 검증하기

수정한 파일은 일부 문자열만 loads()에 넣어 확인하지 말고 전체를 검증하세요.

python -m json.tool input.json

이 명령은 파일이 JSON으로 해석되는지 확인하고 보기 좋은 형태로 출력합니다. 명령 방식은 Python 버전에 따라 공식 문서의 현재 안내를 함께 확인하는 것이 좋습니다. 실패한다면 표시된 줄과 열을 기준으로 해당 위치 앞뒤의 쉼표와 따옴표를 다시 보세요.

자동화에 적용할 판단 순서

저라면 다음 순서로 좁혀 갑니다.

  • JSONDecodeError의 메시지와 줄·열·문자 위치를 기록합니다.
  • repr()으로 파서에 전달된 실제 문자열을 확인합니다.
  • 입력이 Python dict의 str() 결과인지, 외부에서 받은 JSON인지 구분합니다.
  • 작은따옴표, 키 인용, 마지막 쉼표, 주석, 여러 문서 여부를 확인합니다.
  • Python 객체라면 json.dumps()로 직렬화하고, 파일이라면 python -m json.tool로 전체를 검증합니다.

이 순서를 지키면 오류 메시지를 보고 무작정 따옴표만 바꾸는 일을 피할 수 있습니다. 이후에 필수 키나 값의 타입을 검증하는 것은 문법 파싱이 성공한 다음 단계입니다.

참고 자료

Leave a Comment