What is NDJSON?
NDJSON — Newline-Delimited JSON — is a streaming text format where each line is one independent, valid JSON value, separated by \n. There is no enclosing array, no comma between records, no surrounding document. NDJSON is the same format as JSON Lines (JSONL); the two names are interchangeable. NDJSON is the spelling preferred in the JavaScript / Node ecosystem and pushed by ndjson.org; JSONL is the spelling preferred in Python, ML, and data engineering.
The shape
{"id":1,"event":"signup","ts":"2026-05-22T08:14:01Z"}
{"id":2,"event":"click","ts":"2026-05-22T08:14:03Z","path":"/pricing"}
{"id":3,"event":"checkout","ts":"2026-05-22T08:14:11Z","amount":29}
Each line is parseable on its own with JSON.parse (or any RFC 8259 parser). The file as a whole is not a single JSON document — you must split on \n first and then parse each line. This is the entire format. There is no header, no schema, no metadata.
Quick facts
| Property | Value |
|---|---|
| Format name | Newline-Delimited JSON |
| Common nicknames | NDJSON, JSONL, JSON Lines, LDJSON, line-delimited JSON |
| File extension | .ndjson (also accepts .jsonl, .ldjson) |
| MIME type | application/x-ndjson |
| Text encoding | UTF-8 |
| Line separator | \n (LF). Most parsers also accept \r\n. |
| Comments | Not allowed |
| Origin | ndjson.org / github.com/ndjson family of npm packages, ~2013 |
| Equivalent to | JSON Lines (.jsonl), LDJSON (.ldjson) |
Why NDJSON exists
Regular JSON is a single document. To send a sequence of records, you have to either:
- Wrap them in a JSON array (
[record, record, record]) — which means the consumer cannot read record 1 until the whole array has arrived, and you cannot append a record without rewriting the file. - Send them framed somehow — which is what NDJSON does, using the simplest possible framing: one record per line.
NDJSON solves the streaming problem with one rule change: delete the array brackets and the commas, separate records with newlines. Everything else falls out of that: you can stream, append, tail, grep, and split the file with normal Unix tooling, and one corrupt record doesn't poison the rest.
Where NDJSON is used
- HTTP streaming responses. When an API returns many records and you want the client to start processing immediately, NDJSON with
Content-Type: application/x-ndjsonand chunked transfer encoding is the canonical pattern. OpenAI and Anthropic streaming completions use this shape. - Structured application logs. Node's
pino, Go'sslog, Python'sstructlog, Java'slogbackJSON encoder — all emit one JSON object per line. - Observability pipelines. Datadog, Loggly, New Relic, Vector, Fluentbit, Logstash all natively ingest NDJSON.
- Search ingest. Elasticsearch and OpenSearch's
_bulkAPI consumes NDJSON for batch indexing. - D3 / data viz. The npm
ndjsonfamily of streaming parsers is part of Mike Bostock's data-viz toolchain. - Server-Sent Events alternatives. NDJSON-over-HTTP is a popular alternative to SSE for streaming structured data to browsers.
- ML inference batching. Many model-serving runtimes accept NDJSON for batched-prediction requests.
NDJSON rules
- One JSON value per line.
- Lines separated by
\n(LF). - Each line, in isolation, is valid JSON per RFC 8259.
- UTF-8 encoded, without BOM.
- No comments, no trailing commas, no
NaN/Infinity(same as JSON). - Blank lines are not in the spec; most parsers tolerate them, strict parsers reject.
- Trailing
\nafter the last record is recommended but not required.
That's the entire specification. For the formal rules and the edge cases parsers disagree on — these apply to NDJSON identically — see the JSONL specification.
NDJSON vs JSONL — the relationship
NDJSON and JSONL are the same format. The difference is purely the name and which ecosystem promotes it:
- NDJSON — promoted by ndjson.org. Dominant in JavaScript, Node, observability, and HTTP streaming.
- JSONL — promoted by jsonlines.org. Dominant in Python, data engineering, and ML training pipelines (HuggingFace, OpenAI fine-tunes).
- LDJSON — historical synonym, now rare.
If you receive an .ndjson file and your tool expects .jsonl (or vice versa), just rename it — the content is identical. Use the NDJSON ↔ JSONL normalizer if you also need to strip a BOM or normalize line endings in the same pass. For a deeper side-by-side, see NDJSON vs JSONL.
NDJSON over HTTP
The canonical streaming pattern:
HTTP/1.1 200 OK
Content-Type: application/x-ndjson
Transfer-Encoding: chunked
{"id":1,"event":"signup"}
{"id":2,"event":"click"}
{"id":3,"event":"checkout"}
Each chunk ends at a \n boundary so the client can parse complete records as bytes arrive. Producers should flush after each record (or every N records). Consumers should accumulate bytes until the next \n, then parse and emit.
Minimal Node consumer:
const res = await fetch(url);
const reader = res.body.getReader();
const dec = new TextDecoder();
let buf = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buf += dec.decode(value, { stream: true });
let i;
while ((i = buf.indexOf('\n')) >= 0) {
const line = buf.slice(0, i).trim();
buf = buf.slice(i + 1);
if (line) handle(JSON.parse(line));
}
}
NDJSON gotchas
- BOM at the start. Some Windows producers emit
EF BB BFbefore the first record. Many parsers treat it as part of the first line, breaking the record. Strip on read; never emit. - CRLF line endings. Tolerated by most parsers but not strictly NDJSON. Normalize to LF for portability.
- Multi-line JSON. Pretty-printed JSON spanning several lines is not NDJSON. Minify each record first.
- Embedded newlines in strings. Must be escaped as
\ninside the JSON value, never raw. - Heterogeneous shapes. Allowed by the spec, but most consumers assume one schema per file. Stick to it.
- Mistaking NDJSON for JSON. If you
JSON.parsea whole NDJSON file, you'll get a parse error on the first newline. Split first.
NDJSON in three languages
Node (streaming):
const { createReadStream } = require('fs');
const readline = require('readline');
const rl = readline.createInterface({ input: createReadStream('events.ndjson') });
rl.on('line', l => { if (l.trim()) handle(JSON.parse(l)); });
Python (streaming):
import json
with open('events.ndjson', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line:
handle(json.loads(line))
Go (streaming):
scanner := bufio.NewScanner(f)
scanner.Buffer(make([]byte, 64*1024), 16*1024*1024)
for scanner.Scan() {
var r Record
if err := json.Unmarshal(scanner.Bytes(), &r); err == nil {
handle(r)
}
}
Tools on this site that work on NDJSON
- NDJSON Validator — per-line syntax check.
- NDJSON ↔ JSONL — strip BOM, normalize CRLF, drop blank lines, download with either extension.
- Viewer — record-by-record tree / grid view.
- Auto-fixer — repair trailing commas, single quotes, smart quotes, comments.
- Formatter — pretty-print or minify each record individually.
- Query (jq-style) — slice, filter, reshape with jq syntax.
- NDJSON → CSV — flatten nested keys and download a spreadsheet-ready file.
All tools accept both .ndjson and .jsonl; the file picker explicitly allows both extensions.
FAQ
Is NDJSON different from JSONL?
No. Same format, different name. NDJSON is the name preferred by the JavaScript / Node / observability ecosystem; JSONL is preferred in Python, ML, and data engineering. Both mean "one JSON value per line, separated by \n." See NDJSON vs JSONL for the side-by-side.
Can I parse NDJSON with JSON.parse?
Not the whole file at once — JSON.parse expects a single document. Split on \n first, then call JSON.parse on each non-empty line.
What MIME type should I use?
application/x-ndjson. It's the most widely recognized by HTTP clients (curl, httpie, Postman) and CDNs. Avoid application/json for NDJSON — clients will try to parse the whole body as one document.
Can NDJSON records span multiple lines?
No. Records are delimited by line breaks. Any newline inside a string value must be escaped as \n.
Is NDJSON the same as JSON Streaming?
NDJSON is one variant of JSON streaming. Another variant is "JSON text sequences" (RFC 7464), which uses RS (0x1E) as the record separator. NDJSON is by far the more common variant in real-world systems.
What about gzipped NDJSON?
.ndjson.gz compresses very well because repeated keys deduplicate efficiently. Gzip typically shrinks NDJSON by 80–95%. DuckDB, jq, Node's zlib, and Python's gzip all read compressed NDJSON natively.
— S., [email protected]