JSONL 파일을 JSON으로 열면 오류가 나는 이유
대용량 로그나 데이터 수집 결과물을 다룰 때 확장자가 .jsonl인 파일을 접하는 경우가 많습니다. 겉보기에는 각 행이 중괄호 {}로 감싸진 JSON 객체 형태여서 일반적인 JSON 파서에 통째로 넘기기 쉽지만, 실행하는 즉시 구문 분석 오류를 마주하게 됩니다.
단일 JSON 문법의 쉼표나 따옴표 누락은 JSON 오류 찾기: 쉼표·따옴표·긴 숫자 확인에서 점검할 수 있지만, JSONL 파일 전체를 한 번에 읽을 때 일어나는 오류는 파일의 규격 자체가 다르기 때문에 발생합니다. JSONL과 일반 JSON 배열의 구조적 차이와 줄 단위 역직렬화 절차를 살펴봅니다.
JSON 배열과 JSON Lines 형식의 구조적 차이
JSON Lines 규격은 레코드를 한 번에 하나씩 스트림 형태로 처리할 수 있도록 고안된 텍스트 형식입니다. 각 줄은 독립적으로 유효한 단일 JSON 값이어야 하며, 줄바꿈 문자(\n)로 서로를 구분합니다.
| 항목 | 표준 JSON 배열 | JSON Lines (.jsonl) |
|---|---|---|
| 최상위 구조 | 전체 파일이 단 하나의 대괄호 [...] 배열로 묶임 | 각 행마다 독립된 최상위 JSON 값(주로 객체) 존재 |
| 항목 구분자 | 항목 사이의 쉼표(,) | 줄바꿈 문자(\n, CRLF의 \r\n도 허용) |
| 메모리 적재 방식 | 전체 문서를 한 번에 읽어 트리를 구성 | 줄 단위(Line-by-line)로 한 행씩 순차 파싱 가능 |
| 빈 줄 허용 여부 | 공백 및 줄바꿈이 자유롭게 허용됨 | 빈 줄(공백만 있는 줄)은 유효하지 않은 값으로 간주 |
일반 JSON 문서는 전체가 하나의 유효한 루트 값(배열이나 객체 등)으로 구성되어야 합니다. 반면 JSONL은 여러 개의 유효한 JSON 값이 파일 하나에 줄바꿈으로 나열되어 있을 뿐, 전체 텍스트를 감싸는 최상위 컨테이너가 없습니다.
전체 파일을 통째로 파싱할 때 Extra data가 발생하는 원인
표준 파서인 Python json.loads나 유사한 파싱 함수는 입력 텍스트 전체에서 단 하나의 완성된 JSON 문서를 기대합니다. 파서가 첫 번째 줄의 닫는 중괄호 }를 만나면 하나의 완전한 JSON 객체 해석을 정상적으로 끝마칩니다.
하지만 바로 다음 바이트에 줄바꿈과 함께 두 번째 객체의 여는 중괄호 {가 나타나면, 파서는 유효한 JSON 문서 종료 위치 뒤에 추가 바이트가 존재한다고 판단하여 JSONDecodeError: Extra data 예외를 발생시킵니다. 내부적으로 첫 객체의 끝 인덱스 뒤에 공백이 아닌 데이터가 남아 있기 때문입니다.
{
"input": "{\"id\":\"A01\"}\n{\"id\":\"A02\"}\n",
"line_by_line": [
{
"id": "A01"
},
{
"id": "A02"
}
],
"whole_file_error": "Extra data",
"array_equivalent": [
{
"id": "A01"
},
{
"id": "A02"
}
]
}레코드별 오류 위치 추적과 여러 줄 문자열 이스케이프
JSONL 파일의 장점은 한 행이 잘못되어도 몇 번째 줄에서 파싱 실패가 일어났는지 즉시 특정할 수 있다는 점입니다. 텍스트 파일의 1번 줄이 첫 번째 레코드가 되므로, 루프를 돌며 예외가 발생한 인덱스를 기록하면 수백만 건 중 결함이 있는 특정 행만 골라낼 수 있습니다.
이때 주의해야 할 점은 텍스트 내부의 실제 줄바꿈 처리입니다. JSON Lines 형식의 제2요건과 제3요건에 따라 각 줄은 그 자체로 완전한 JSON 값이어야 하며 줄 구분자는 반드시 줄바꿈이어야 합니다. 따라서 레코드 내부의 문자열 값에 줄바꿈이 포함될 때는 물리적 개행을 넣으면 안 되며, 반드시 이스케이프된 \n 문자열로 직렬화되어 한 줄 안에 머물러야 합니다.
# 잘못된 형태: 문자열 내부 줄바꿈으로 한 레코드가 두 줄로 쪼개짐
{"id": "A01", "desc": "첫째줄
둘째줄"}
# 올바른 형태: 줄바꿈 문자를 이스케이프하여 한 줄에 유지
{"id": "A01", "desc": "첫째줄\n둘째줄"}물리적 줄바꿈이 문자열 내부에 그대로 들어가면 첫 번째 행은 닫히지 않은 문자열로 간주되어 문법 오류가 나고, 두 번째 행 또한 불완전한 파편이 되어 연쇄적으로 파싱에 실패합니다.
안전한 JSONL 읽기 및 점검 순서
파일 확장자가 .jsonl이거나 줄 단위 로그 파일인 경우 다음과 같은 순서로 데이터를 다루는 것이 안전합니다.
- 파일을 텍스트 줄 단위로 열기: 전체를 한 번에 메모리에 올리지 않고 파일 객체의 반복자(iterator)나 행 읽기 메서드를 사용하여 한 줄씩 스트림으로 순회합니다.
- 공백 제거 및 빈 줄 확인: 각 행의 양쪽 개행 문자를 다듬되, 내용이 전혀 없는 빈 줄이 섞여 있는지 검사합니다. JSON Lines 규격에서
null은 유효한 값이지만 내용이 없는 빈 행은 유효한 값이 아닙니다. - 개별 행 역직렬화: 가져온 한 줄의 텍스트를 파서 함수에 넘겨 딕셔너리나 객체로 변환합니다. 예외가 발생하면 해당 행 번호(Line number)를 기록합니다.
- 배열 형태로의 필요성 판단: 모든 데이터를 메모리에 모아 단일 JSON 배열로 저장해야 한다면 각 객체를 리스트에 누적한 뒤 대괄호로 둘러싸인 표준 JSON 형식으로 다시 내보냅니다.
실패 사례와 검증 한계
JSONL 파싱을 시도할 때 다음과 같은 실패 상황이 자주 발생합니다.
- 파일 첫머리의 BOM(Byte Order Mark): JSON Lines 규격은
U+FEFF바이트 순서 표식을 포함하지 않도록 규정합니다. UTF-8 BOM이 파일 맨 앞에 붙어 있으면 첫 번째 줄을 파싱할 때 알 수 없는 문자로 인해 디코딩 오류가 발생합니다. 인코딩 상태 점검은 한글 파일 깨짐: UTF-8 인코딩 확인하는 순서를 참고할 수 있습니다. - 줄바꿈 형식의 불일치: 규격상
\n과\r\n은 둘 다 허용되지만 운영체제 간 전송 과정에서 예기치 않은 제어 문자가 남을 수 있습니다. 줄바꿈 문자의 차이는 줄바꿈 CRLF와 LF 차이에서 구분하는 방법을 다룹니다. - 마지막 행의 빈 줄: 생성 도구에 따라 파일 끝에 개행을 하나 더 추가하는 경우가 권장되지만, 이를 단순
split('\n')으로 쪼갤 경우 마지막 요소로 빈 문자열""이 생성되어 파싱 에러를 유발합니다.
다만 줄 단위 구문 검사 방식은 각 행의 JSON 문법 적합성만 판별할 뿐, 이전 줄과 다음 줄 사이의 ID 중복이나 데이터 논리 구조의 무결성까지 자동으로 검증하지는 못합니다. 스키마와 업무 규칙 검증은 파싱 성공 이후 별도 단계에서 수행해야 합니다.
참고 자료와 확인 범위
아래 자료는 형식·기능의 정의를 확인하는 데 사용했습니다. 본문의 예제와 설명은 이 사이트의 작업 상황에 맞춰 구성했습니다. 제품 메뉴는 버전과 언어에 따라 다를 수 있습니다.
확인일: 2026-10-19 · 오류를 발견하면 이 글의 오류 신고로 알려주세요.