- 공유 링크 만들기
- X
- 이메일
- 기타 앱
Python에서 API 응답이나 크롤링 결과를 json.loads()로 파싱하려다 "json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)" 에러를 마주한 경험이 있을 것입니다. 에러 메시지만으로는 원인을 특정하기 어렵고, 육안으로 JSON 문자열을 확인해도 문제를 찾기 쉽지 않습니다. 이 글에서는 에러 발생 원인을 빠르게 진단하고, 문자열 조작과 검증 도구를 활용해 파싱 문제를 해결하는 실전 방법을 정리합니다.
| Python JSONDecodeError Expecting value line 1 column 1 해결 완벽 가이드 |
1. JSONDecodeError 발생 원인 진단
JSON 파싱 에러는 크게 세 가지 원인으로 분류할 수 있습니다.
- 인코딩 문제: API 응답을 잘못된 인코딩으로 디코딩하거나, response.text 대신 response.content를 직접 json.loads()에 넘긴 경우
- 빈 응답: API가 200 OK를 반환했지만 실제 body가 비어 있거나, Filter/Middleware 단계에서 스트림이 소비되어 컨트롤러까지 데이터가 전달되지 않은 경우
- JSON 구조 오류: 작은따옴표, 트레일링 쉼표, 닫히지 않은 괄호, 허용되지 않는 특수문자 등 JSON 표준을 위반한 문자열
첫 번째로 확인해야 할 것은 응답이 실제로 존재하는지입니다. 다음 코드로 응답 내용을 출력해 보십시오.
response = requests.get(url)
print(f"Status: {response.status_code}")
print(f"Content-Type: {response.headers.get('Content-Type')}")
print(f"Body length: {len(response.text)}")
print(response.text[:500]) # 앞 500자만 출력
만약 Body length가 0이거나 HTML/XML이 반환된다면, JSON 파싱 이전에 API 엔드포인트나 인증 헤더를 먼저 점검해야 합니다.
2. 에러 위치 정보를 활용한 문제 지점 특정
JSONDecodeError 객체는 colno, lineno, pos 속성을 제공하여 문제가 발생한 정확한 위치를 알려줍니다. 이를 활용하면 긴 JSON 문자열에서 에러 지점을 빠르게 찾을 수 있습니다.
import json
try:
data = json.loads(result_text)
except json.JSONDecodeError as e:
print(f"Error at line {e.lineno}, column {e.colno}")
print(f"Position: {e.pos}")
# 에러 주변 ±10자 출력
start = max(0, e.pos - 10)
end = min(len(result_text), e.pos + 10)
print(f"Context: ...{result_text[start:end]}...")
이 방법으로 에러 주변 문자열을 확인했을 때도 원인을 파악하기 어렵다면, jsonlint.com 같은 온라인 검증 도구에 전체 문자열을 붙여넣어 보십시오. "Expecting 'STRING', got '{'" 같은 구체적인 에러 메시지를 얻을 수 있습니다.
3. 문자열 조작을 통한 JSON 수정 패턴
서버가 표준 JSON이 아닌 변형된 형식을 반환하는 경우, 문자열 치환(str.replace)으로 구조를 교정한 뒤 파싱하는 것이 현실적인 해결책입니다. 실무에서 자주 발생하는 수정 패턴은 다음과 같습니다.
- 작은따옴표 → 큰따옴표 변환:
result_text.replace("'", '"')(단, 값 내부에 작은따옴표가 있는 경우 정규식 사용 필요) - 객체를 배열로 감싸기: 서버가 단일 객체를 반환했지만 클라이언트가 배열을 기대하는 경우
result_text = '[' + result_text + ']' - 특정 키를 배열로 변환:
result_text.replace('"body":{', '"body":[{')및 대응하는 닫힘 괄호 조정 - 트레일링 쉼표 제거:
result_text.replace(',}', '}').replace(',]', ']') - 불완전한 닫힘 괄호 보정: 응답이 중간에 잘린 경우
result_text += ']}'등으로 수동 완성
예를 들어, 서버가 다음과 같은 형식을 반환했다면:
{"status":"ok","body":{"id":1,"name":"test"}}
body를 배열로 변경하려면:
result_text = result_text.replace('"body":{', '"body":[{')
result_text = result_text.replace('}}', '}]}')
data = json.loads(result_text)
주의: 이 방식은 응답 구조가 일정한 경우에만 안전합니다. 구조가 자주 바뀌는 API라면 서버 측 수정을 요청하거나, 정규식 기반의 더 정교한 파싱을 고려해야 합니다.
4. 파싱 결과를 DataFrame으로 변환
JSON 파싱에 성공했다면, pandas.json_normalize()를 사용해 중첩된 JSON을 평탄화된 DataFrame으로 변환할 수 있습니다. 이는 데이터 분석과 CSV 저장에 유용합니다.
import pandas as pd
data = json.loads(result_text)
df = pd.json_normalize(data['body'])
print(df.head())
df.to_csv('output.csv', index=False)
json_normalize는 중첩된 딕셔너리와 리스트를 자동으로 컬럼으로 펼쳐주므로, 복잡한 API 응답을 테이블 형태로 빠르게 변환할 수 있습니다.
요약 및 Action Item
JSONDecodeError는 인코딩 문제, 빈 응답, JSON 구조 오류 세 가지 원인으로 발생합니다. 에러 객체의 colno/pos 정보로 문제 지점을 특정하고, jsonlint로 구조를 검증한 뒤, 문자열 치환으로 수정하여 파싱하는 것이 핵심 해결 흐름입니다. 당장 실행할 Action Item: 현재 발생한 에러의 e.pos 값을 출력하고, 해당 위치 ±20자를 확인하여 작은따옴표, 트레일링 쉼표, 닫히지 않은 괄호 중 무엇이 문제인지 파악하십시오. 반복적인 패턴이라면 str.replace를 함수로 래핑하여 재사용 가능한 전처리 파이프라인을 구축하는 것을 권장합니다.
#함께 읽으면 좋은 글
Python IndexError list index out of range 원인과 5가지 해결 방법 : 바로보기
댓글
댓글 쓰기