워로디스

JSONL, NDJSON 정리 본문

정리/데이터직렬화형식

JSONL, NDJSON 정리

워로디스 2026. 5. 3. 20:38

1. JSONL / NDJSON이란

JSONL(JSON Lines)NDJSON(Newline Delimited JSON)은 거의 같은 형식이다.

핵심 규칙은 다음과 같다.

한 줄에 하나의 유효한 JSON value

예:

{"id":1,"text":"hello"}
{"id":2,"text":"world"}
{"id":3,"text":"line1\nline2"}

주로 로그, 이벤트 스트림, 대용량 데이터셋, ML 학습 데이터, 배치 처리 등에 사용된다.

2. JSONL과 NDJSON의 차이

항목 JSONL NDJSON
풀네임 JSON Lines Newline Delimited JSON
확장자 .jsonl .ndjson
자주 쓰이는 맥락 파일, 데이터셋, ML 스트리밍 API, HTTP 응답
실질적 포맷 차이 거의 없음 거의 없음

실무적으로는 이렇게 봐도 된다.

JSONL == NDJSON

다만 HTTP 스트리밍 응답에서는 보통 이런 Content-Type을 쓴다.

Content-Type: application/x-ndjson

3. 줄바꿈 문자가 JSON 안에 들어가는 경우

JSONL에서 레코드는 실제 개행 문자로 구분된다.

따라서 JSON 문자열 안의 줄바꿈은 실제 줄바꿈이 아니라 \n으로 escape되어야 한다.

정상:

{"message":"hello\nworld"}
{"message":"next record"}

비정상:

{"message":"hello
world"}
{"message":"next record"}

JSON 문자열 안에는 실제 개행 문자를 직접 넣을 수 없다. 반드시 \n으로 표현해야 한다.

4. 각 라인의 시작 문자는 {, [로만 한정되지 않는다

JSONL의 각 라인은 object일 필요는 없다.

각 라인은 하나의 유효한 JSON value이면 된다.

가능한 시작 문자:

시작 문자 의미
{ object
[ array
" string
-, 09 number
t true
f false
n null

예를 들어 아래는 모두 유효한 JSONL이다.

{"id":1}
[1,2,3]
"hello"
123
true
false
null

null도 포맷상으로는 정합하다.

다만 실무에서는 대부분 object per line을 기대한다.

{"id":1,"message":"hello"}
{"id":2,"message":"world"}

object 형태가 필드 확장, 스키마 관리, downstream 처리에 유리하기 때문이다.

5. 문자열은 반드시 큰따옴표가 필요하다

JSON value가 문자열이면 반드시 double quote로 감싸야 한다.

정상:

"hello"
{"message":"hello"}

비정상:

hello
'hello'

hello는 raw text일 뿐 JSON 문자열이 아니다.

JSON 문자열은 반드시 다음 형태여야 한다.

"hello"

6. Spring Boot RestControllerString 반환과 JSON 문자열은 다르다

Spring Boot에서 String을 반환하면 보통 raw text response가 된다.

@GetMapping("/hello")
public String hello() {
    return "hello";
}

응답 body는 보통 이렇게 나온다.

hello

하지만 이것은 JSON 문자열이 아니다.

JSON 문자열이라면 body가 이렇게 나와야 한다.

"hello"

즉 다음은 서로 다르다.

HTTP raw text       -> hello
JSON string value   -> "hello"
JSONL string record -> "hello"

따라서 JSONL/NDJSON으로 문자열 레코드를 보내려면 아래처럼 quote가 있어야 한다.

"hello"
"world"

보통은 object로 감싸는 것이 더 안전하다.

{"message":"hello"}
{"message":"world"}

7. 성능 관점

JSONL에서 각 JSON을 분리하는 작업은 보통 큰 병목이 아니다.

레코드 구분자는 실제 newline byte인 0x0A이다.

{"id":1,"text":"hello\nworld"}\n
{"id":2,"text":"next"}\n

여기서 문자열 내부의 \n은 실제 개행 byte가 아니라 \n 두 문자다.

따라서 레코드 경계를 찾을 때는 실제 newline byte만 찾으면 된다.

성능 병목은 보통 다음 쪽에서 먼저 발생한다.

I/O
압축 해제
UTF-8 디코딩
JSON 파싱
객체 생성
후처리 로직

피해야 할 방식:

data = f.read()
lines = data.splitlines()

권장 방식:

with open("data.jsonl", "rb") as f:
    for line in f:
        obj = json.loads(line)

성능이 더 중요하면 orjson, simdjson 같은 빠른 파서를 고려할 수 있다.

8. 병렬 처리

JSONL은 큰 파일을 byte range로 나누어 병렬 처리하기 좋다.

각 worker가 자기 시작 위치에서 다음 newline까지 이동하면 레코드 경계를 맞출 수 있다.

반대로 일반 JSON 배열은 중간 offset에서 안전하게 시작하기 어렵다.

[
  {"id":1},
  {"id":2}
]

이 경우 JSON 구조를 이해해야 하기 때문이다.

단, .jsonl.gz는 gzip 특성상 중간 지점부터 독립적으로 풀기 어려워 병렬 처리에 불리할 수 있다.

9. JVM에서의 처리

JVM에서는 보통 JSONL 전용 파서보다는 다음 조합을 쓴다.

라인 단위 입력 + JSON 파서

가장 무난한 기본 선택은 Jackson이다.

ObjectMapper mapper = new ObjectMapper();
ObjectReader reader = mapper.readerFor(Event.class);

try (BufferedReader br = Files.newBufferedReader(
        Path.of("events.jsonl"),
        StandardCharsets.UTF_8
)) {
    String line;
    while ((line = br.readLine()) != null) {
        if (line.isBlank()) continue;

        Event event = reader.readValue(line);
        // process event
    }
}

이 방식의 장점:

라인 번호 추적 가능
깨진 레코드 skip 가능
ObjectReader 재사용 가능
구현이 단순함

고성능이 더 필요하면 다음 후보를 벤치마크해볼 수 있다.

라이브러리 특징
Jackson 안정적인 기본 선택
DSL-JSON 고성능 JVM JSON databinding
jsoniter-java 빠른 Java JSON parser
simdjson-java SIMD 기반 고성능 파서 계열

10. 실무 권장 형태

대부분의 경우 JSONL/NDJSON은 object per line으로 쓰는 것이 가장 안전하다.

{"id":1,"type":"event","payload":{"message":"hello"}}
{"id":2,"type":"event","payload":{"message":"world"}}

권장사항은 다음과 같다.

1. 한 줄에 하나의 JSON value만 둔다.
2. 문자열 내부 줄바꿈은 \n으로 escape한다.
3. 문자열 value는 반드시 "..."로 감싼다.
4. 실무에서는 object per line을 기본으로 한다.
5. 전체 파일 read + split보다 스트리밍 처리를 사용한다.
6. JVM에서는 Jackson + ObjectReader 재사용이 무난하다.
7. 병목은 줄 분리보다 JSON 파싱, 객체 생성, I/O, 압축 해제 쪽일 가능성이 높다.
8. Spring Boot의 raw String 응답은 JSON 문자열과 다르다.

결론

JSONL/NDJSON의 핵심 원칙은 하나다.

각 물리적 라인은 독립적으로 파싱 가능한 하나의 유효한 JSON value여야 한다.

문자열 레코드는

hello

가 아니라

"hello"

여야 한다.

그리고 일반적인 데이터 레코드는 보통 다음처럼 object 형태로 두는 것이 가장 안전하다.

{"message":"hello"}

'정리 > 데이터직렬화형식' 카테고리의 다른 글

JSON Schema 치트시트  (0) 2026.05.03
JSON Schema 정리  (0) 2026.05.03