파이썬 JSONDecodeError Extra data 해결: 여러 JSON 객체와 JSON Lines 구분법

JSONDecodeError: Extra data가 발생하면 첫 번째 JSON 값은 읽혔지만 그 뒤에 다른 값이나 문자열이 남아 있는지 먼저 확인해야 합니다. 하나의 JSON 문서라면 객체들을 배열 안에 저장하고, 줄마다 독립된 값이면 JSON Lines로 한 줄씩 읽으며, 구분자 없이 이어진 값은 JSONDecoder.raw_decode()로 경계를 찾아 처리하는 방식이 맞습니다. 무조건 대괄호를 붙이거나 예외를 무시하기보다 파일의 실제 구조와 앞으로의 저장 방식을 함께 결정해야 합니다.

Extra data는 첫 번째 값 뒤에 내용이 남았다는 뜻입니다

json.load()와 json.loads()는 입력 전체를 하나의 유효한 JSON 문서로 해석합니다. 예를 들어 파일이 다음처럼 객체를 이어 붙인 형태라면 첫 객체 뒤의 {부터는 같은 문서의 일부로 해석할 수 없어 Extra data가 발생합니다.

{"job": "backup"}{"job": "report"}

줄바꿈만 넣은 경우도 json.loads()에 파일 전체를 한 번에 넘기면 같은 문제가 생길 수 있습니다.

{"job": "backup"}
{"job": "report"}

이 오류를 “JSON 문법이 전부 깨졌다”라고 단정할 필요는 없습니다. 첫 번째 값은 정상이고, 뒤에 독립된 값이 남은 상황일 수 있기 때문입니다. 먼저 파일의 앞부분과 오류 위치 주변을 확인해 객체가 배열 안에 있는지, 줄마다 하나씩 있는지, 값 사이에 구분자가 전혀 없는지를 구분합니다.

하나의 JSON 문서로 관리한다면 배열로 저장합니다

데이터를 한 파일에서 하나의 문서로 조회하고 함께 교체할 목적이라면 최상위 값을 배열로 두는 편이 분명합니다.

[
  {"job": "backup"},
  {"job": "report"}
]

Python에서는 배열 전체를 읽은 뒤 요소를 순회할 수 있습니다.

import json

with open("jobs.json", encoding="utf-8") as file:
    jobs = json.load(file)

for job in jobs:
    print(job["job"])

기존 파일을 배열로 바꿀 때는 원본을 덮어쓰기 전에 각 객체가 실제로 분리되는지 확인하고, 변환 결과의 항목 수와 원본의 입력 범위를 대조하는 절차를 두는 것이 안전합니다. 단순히 텍스트 앞뒤에 대괄호를 붙이면 객체 사이에 쉼표가 없는 파일은 여전히 유효한 JSON이 되지 않습니다.

줄마다 값이 독립적이면 JSON Lines로 읽습니다

로그나 수집 결과처럼 새 기록을 계속 추가하고 각 기록을 따로 처리할 목적이면 JSON Lines가 자연스럽습니다. 이 형식은 각 줄에 하나의 유효한 JSON 값을 담고, 줄바꿈을 기준으로 값을 나눕니다.

{"job": "backup", "ok": true}
{"job": "report", "ok": true}

Python에서는 파일을 순회하면서 빈 줄을 정책에 따라 건너뛰고 각 줄을 별도로 파싱할 수 있습니다.

import json

with open("jobs.jsonl", encoding="utf-8") as file:
    for line_number, line in enumerate(file, start=1):
        if not line.strip():
            continue
        try:
            job = json.loads(line)
        except json.JSONDecodeError as error:
            raise ValueError(f"JSON Lines {line_number}번째 줄을 확인하세요") from error
        print(job["job"])

이 방식에서는 한 줄의 오류를 조용히 삼키기보다 줄 번호와 원본을 별도 로그로 남길지 결정해야 합니다. 자동화 입력이라면 깨진 줄을 건너뛰는 정책이 데이터 누락을 숨길 수 있으므로, 중단·격리·재처리 중 하나를 명시하는 편이 좋습니다.

구분자 없이 이어진 JSON 값은 raw_decode를 검토합니다

입력이 JSON 객체나 배열을 구분자 없이 연속해서 담은 스트림이라면 json.loads()를 전체 문자열에 적용할 수 없습니다. JSONDecoder.raw_decode()는 문자열의 앞부분에서 JSON 값 하나를 읽고 그 값이 끝난 인덱스를 돌려주므로, 남은 문자열을 다시 읽는 방식의 경계 탐색에 사용할 수 있습니다.

import json

text = '{"job": "backup"}{"job": "report"}'
decoder = json.JSONDecoder()
index = 0
values = []

while index < len(text):
    while index < len(text) and text[index].isspace():
        index += 1
    if index == len(text):
        break

    value, next_index = decoder.raw_decode(text, index)
    values.append(value)
    index = next_index

print(values)

이 처리는 입력이 정말 연속 JSON 스트림이라는 전제가 있어야 합니다. 스트림에 구분 규칙이 없으면 잘못 붙은 문자열이나 잘린 값도 섞일 수 있으므로, 가능하면 생산 단계에서 JSON Lines처럼 경계가 분명한 형식으로 바꾸는 편이 운영하기 쉽습니다.

세 가지 선택을 저장 방식과 함께 판단합니다

상황 읽기 방식 앞으로의 저장 방식
관련 데이터를 하나의 문서로 다룸 json.load()로 배열 읽기 하나의 배열로 저장
기록을 계속 추가하고 줄 단위로 처리함 줄마다 json.loads() JSON Lines로 저장
이미 구분자 없이 이어진 값이 있음 raw_decode()로 경계 탐색 입력 생산 단계에서 경계 있는 형식 검토

판단 순서는 간단합니다. 파일 전체가 하나의 문서여야 하는지, 각 기록을 독립적으로 추가·처리해야 하는지부터 정합니다. 그 다음 현재 파일을 변환할지, 읽기 호환 코드를 둘지, 생산하는 쪽의 저장 형식을 바꿀지를 선택합니다. Extra data 예외만 없애는 것이 목표가 아니라 다음 append에서도 같은 문제가 재발하지 않게 하는 것이 핵심입니다.

마무리: 파일을 고치기 전에 형식을 고정합니다

Extra data는 첫 번째 JSON 뒤에 추가 내용이 남았다는 신호인 경우가 많습니다. 배열이 필요한 단일 문서인지, JSON Lines로 관리할 기록인지, 이미 붙어 버린 스트림을 임시로 읽어야 하는지 구분하면 해결 방법이 달라집니다.

먼저 원본을 보존한 상태에서 입력 경계를 확인하고, 변환 뒤 결과를 검증한 다음 저장 계약을 문서화하십시오. AI 응답이나 외부 API 결과를 파일에 누적하는 자동화라면 응답 하나를 배열에 넣을지, 한 줄의 JSON으로 기록할지부터 정해 두어야 이후 파싱 오류와 복구 범위를 줄일 수 있습니다. 관련된 AI JSON 응답의 형식과 검증을 함께 살펴보려면 파이썬 AI JSON 응답 오류 줄이기: JSON 모드와 Structured Outputs, Pydantic 검증 구분법도 이어서 확인할 수 있습니다.

출처

Leave a Comment