NDJSON을 JSON으로
줄바꿈으로 구분된 레코드를 하나의 배열로 만들고, 실패한 줄을 알려 줍니다.
붙여 넣은 것은 여러분의 브라우저를 떠나지 않습니다. connect-src 허용 목록 덕분에 이는 약속이 아니라 브라우저가 강제하는 보장입니다. 직접 확인하기
줄바꿈으로 구분된 JSON을 배열 하나로 만듭니다. 각 줄은 따로 파싱되고, 실패한 줄은 파일 전체를 무너뜨리는 대신 줄 번호와 함께 보고됩니다.
NDJSON은 로그 파이프라인, BigQuery 내보내기, Elasticsearch bulk 파일, 스트리밍 API에서 나오는 형식입니다. 그리고 그것을 문서 하나로 파싱하려 할 때 "Extra data"와 "unexpected non-whitespace character" 오류를 일으키는 바로 그것입니다.
NDJSON이란
한 줄에 완결된 JSON 값 하나를 두고 줄바꿈으로 구분합니다. 레코드 사이의 쉼표도, 전체를 감싸는 배열도 없습니다. 빈 줄은 무시합니다. 관례적인 확장자는 .ndjson과 .jsonl입니다.
JSON Lines와 NDJSON은 사실상 같은 형식입니다. 두 개의 작은 명세가 중요한 부분에서는 전부 일치합니다. 도구에 따라 부르는 이름이 다를 뿐, 어느 쪽을 염두에 두고 쓴 파일이든 양쪽 다 제대로 읽습니다.
이 형식이 존재하는 이유
실질적인 장점이 셋 있고, 모두 레코드가 서로 독립적이라는 데서 나옵니다.
- 스트리밍이 됩니다
- 받는 쪽이 레코드를 하나씩 처리하고 파일 전체를 들고 있지 않습니다. 50GB 내보내기도 괜찮지만, 50GB짜리 JSON 배열은 그렇지 않습니다.
- 뒤에 덧붙일 수 있습니다
- 레코드를 추가하는 건 파일 끝에 한 번 쓰는 일입니다. JSON 배열에 덧붙이려면 닫는 대괄호를 다시 써야 하고, 그건 덧붙이기가 아닙니다.
- 손상에 강합니다
- 잘못된 줄 하나면 레코드 하나를 잃습니다. JSON 배열에서 바이트 하나가 잘못되면 파일을 통째로 잃습니다.
어느 방향으로 변환할까
문서 하나를 기대하는 곳으로 갈 때는 배열로. 브라우저, 요청 본문, 설정 파일 같은 것들입니다. 파이프라인, 로그, 덧붙이기 전용 파일, 스트리밍하는 무언가로 갈 때는 NDJSON으로. 위에서 두 방향 모두 쓸 수 있습니다.
How to do this in code
코드에서 NDJSON을 읽고 쓰는 방법.
py Python
리스트 컴프리헨션은 전부 메모리에 올립니다. 흐름으로 처리하려면 파일을 직접 순회하세요.
import json
# Read
with open('events.ndjson') as f:
records = [json.loads(line) for line in f if line.strip()]
# Write
with open('events.ndjson', 'w') as f:
for r in records:
f.write(json.dumps(r) + '\n')
# pandas knows the format
import pandas as pd
df = pd.read_json('events.ndjson', lines=True) sh jq
-s는 모든 입력을 배열 하나로 모으고, -c는 한 줄에 압축된 값 하나를 씁니다. 이 두 플래그가 변환의 전부입니다.
# NDJSON to an array
jq -s . events.ndjson > events.json
# An array to NDJSON
jq -c '.[]' events.json > events.ndjson
# Filter a huge NDJSON file without loading it all
jq -c 'select(.level == "error")' events.ndjson js Node
crlfDelay: Infinity를 주면 readline이 CRLF를 하나의 줄바꿈으로 다룹니다. Windows에서 쓴 파일에서는 중요합니다.
import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';
const rl = createInterface({
input: createReadStream('events.ndjson'),
crlfDelay: Infinity,
});
for await (const line of rl) {
if (!line.trim()) continue;
const record = JSON.parse(line);
// one record at a time, constant memory
} 자주 묻는 질문
- NDJSON과 JSON Lines는 같은 건가요?
- 실용적으로는 전부 같습니다. 두 개의 작은 명세가 중요한 부분에서 일치합니다. 한 줄에 JSON 값 하나, UTF-8, 줄바꿈 구분. .jsonl과 .ndjson 확장자도 서로 바꿔 씁니다.
- 레코드 하나가 여러 줄에 걸쳐도 되나요?
- 안 됩니다. 그게 이 형식의 요점입니다. 줄바꿈이 레코드 구분자이므로 각 레코드는 정확히 한 줄에 들어가야 합니다. 쓰기 전에 각 레코드를 압축하세요.
- 제 NDJSON 파일이 왜 JSON으로 파싱되지 않나요?
- 그건 JSON 문서 하나가 아니라 여러 개이기 때문입니다. JavaScript는 "Unexpected non-whitespace character after JSON"을, Python은 "Extra data"를 보고합니다. 둘 다 파서가 값 하나를 다 읽고 나서 또 다른 값을 발견했다는 뜻입니다.