Quick answer
JSON is one document. JSONL (JSON Lines) is one JSON object per line. Use JSON for APIs and config files. Use JSONL for logs, streaming data, large datasets, and any pipeline where loading an entire JSON array into memory is not practical.
What is JSONL?
JSONL (JSON Lines), also called NDJSON (Newline-Delimited JSON), is a text format where every line is a complete, independently valid JSON value — most commonly a JSON object. The file extension is .jsonl or .ndjson. Each line is terminated by a newline (\n).
JSONL is not valid JSON. You cannot paste a .jsonl file into a JSON validator and expect it to pass — it will fail because the file is not a single JSON document. Each individual line is valid JSON; the file as a whole is not.
JSONL vs NDJSON: are they the same?
Yes — JSONL and NDJSON are the same format with different names:
- JSONL = JSON Lines (from jsonlines.org)
- NDJSON = Newline-Delimited JSON (from ndjson.org)
Both specifications define the same rule: one complete JSON value per line, lines separated by \n. The file extensions .jsonl and .ndjson are interchangeable. Tools like Apache Spark, BigQuery, and jq accept both names for the same thing. The difference is purely naming convention — use whichever the tool or API you are working with expects.
A third alias you may encounter is JSON-seq (RFC 7464), which is slightly different — it prefixes each record with a Unicode Record Separator byte. JSON-seq is used by some log formats but is less common than JSONL/NDJSON in practice.
Visual: JSON array vs JSONL (line-by-line)
The core difference in how each format is processed:
JSON (array) JSONL / NDJSON
───────────────────────── ─────────────────────────
[ {"id":1,"event":"login"}
{"id":1,"event":"login"}, {"id":2,"event":"buy"}
{"id":2,"event":"buy"}, {"id":3,"event":"logout"}
{"id":3,"event":"logout"}
]
│ │
▼ ▼
Must load ENTIRE file Read ONE line at a time
into memory before → constant memory
first record is readable → append = one write
→ bad line = skip it
Side-by-side: the same data in JSON and JSONL
Standard JSON (array of objects)
[
{ "id": 1, "event": "login", "user": "alice", "ts": "2026-06-11T09:00:00Z" },
{ "id": 2, "event": "purchase", "user": "bob", "ts": "2026-06-11T09:01:12Z" },
{ "id": 3, "event": "logout", "user": "alice", "ts": "2026-06-11T09:05:44Z" }
]
To read record 2, the parser must load the entire array. To append record 4, you must read the file, parse the JSON, push to the array, and rewrite the whole file — O(n) for every write.
JSONL (one object per line)
{"id":1,"event":"login","user":"alice","ts":"2026-06-11T09:00:00Z"}
{"id":2,"event":"purchase","user":"bob","ts":"2026-06-11T09:01:12Z"}
{"id":3,"event":"logout","user":"alice","ts":"2026-06-11T09:05:44Z"}
To append record 4, open the file in append mode and write one line — O(1). To process 10 million records, read one line at a time — constant memory.
Quick comparison
| Feature | JSON | JSONL |
|---|---|---|
| Structure | Single document (object or array) | One JSON value per line |
| Valid JSON | Yes | No (per file; each line is) |
| File extension | .json | .jsonl, .ndjson |
| Streaming | Not supported | Native — read line by line |
| Memory to read | Full file must be parsed | One line at a time |
| Append a record | Must rewrite the file | Append one line, O(1) |
| Human readability | Pretty-printable, clear structure | Harder to read at scale |
| Typical use cases | APIs, config, small datasets | Logs, ML datasets, ETL pipelines |
| Tool support | Universal | Supported by Spark, BigQuery, Elasticsearch, jq |
| Error isolation | One bad byte breaks the whole file | One bad line, rest still parseable |
The memory problem with large JSON arrays
A standard JSON array must be fully loaded and parsed before you can access a single record. This is fine for a 50 KB API response. It is a serious problem for a 10 GB log file or a 100 million record ML training set.
Consider an event log that grows by 1 million rows per day. Storing it as a JSON array means:
- Reading any record requires loading all preceding records into memory
- Appending one event requires reading the entire file, modifying the array, and writing everything back
- A corrupted byte near the start of the file can prevent parsing any of it
JSONL solves all three: memory is bounded to the size of the largest single line, append is O(1), and a corrupt line only invalidates that one record.
Reading and writing JSONL in Python
The standard json module handles JSONL with a simple loop. If you prefer a cleaner API, the jsonlines package on PyPI wraps the loop and handles edge cases automatically.
import json
# Read JSONL — one record at a time, constant memory
with open('events.jsonl', 'r') as f:
for line in f:
line = line.strip()
if line:
record = json.loads(line)
print(record['event'], record['user'])
# Write JSONL
records = [
{"id": 1, "event": "login", "user": "alice"},
{"id": 2, "event": "purchase", "user": "bob"},
]
with open('events.jsonl', 'w') as f:
for record in records:
f.write(json.dumps(record) + '\n')
# Append a single record without touching the rest of the file
new_event = {"id": 3, "event": "logout", "user": "alice"}
with open('events.jsonl', 'a') as f:
f.write(json.dumps(new_event) + '\n')
Reading JSONL in Node.js
Node’s built-in readline module is all you need. For a higher-level API, the ndjson package on npm provides a Transform stream that emits parsed objects directly.
const fs = require('fs');
const readline = require('readline');
async function readJsonl(filePath) {
const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({ input: fileStream });
for await (const line of rl) {
if (line.trim()) {
const record = JSON.parse(line);
console.log(record.event, record.user);
}
}
}
readJsonl('events.jsonl');
// Write JSONL
const records = [
{ id: 1, event: 'login', user: 'alice' },
{ id: 2, event: 'purchase', user: 'bob' },
];
const output = records.map(r => JSON.stringify(r)).join('\n') + '\n';
fs.writeFileSync('events.jsonl', output);
Convenience libraries
The standard-library approaches above are sufficient for most use cases. For cleaner code or extra features, these well-maintained packages are worth knowing:
# Python: jsonlines package
# pip install jsonlines
import jsonlines
# Read
with jsonlines.open('events.jsonl') as reader:
for obj in reader:
print(obj['event'])
# Write
with jsonlines.open('out.jsonl', mode='w') as writer:
writer.write_all([
{"id": 1, "event": "login"},
{"id": 2, "event": "buy"},
])
// Node.js: ndjson package
// npm install ndjson
const fs = require('fs');
const ndjson = require('ndjson');
// Read — emits parsed objects as a stream
fs.createReadStream('events.jsonl')
.pipe(ndjson.parse())
.on('data', obj => console.log(obj.event));
// Write — serializes objects to NDJSON/JSONL
const serialize = ndjson.stringify();
serialize.pipe(fs.createWriteStream('out.jsonl'));
serialize.write({ id: 1, event: 'login' });
serialize.write({ id: 2, event: 'buy' });
serialize.end();
Converting between JSON and JSONL with jq
jq is the standard command-line tool for JSON processing. It handles JSONL natively when you use the -c (compact) flag.
# JSON array → JSONL
jq -c '.[]' data.json > data.jsonl
# JSONL → JSON array
jq -s '.' data.jsonl > data.json
# Process JSONL: extract one field from each record
jq -r '.user' events.jsonl
# Filter JSONL: only purchase events
jq -c 'select(.event == "purchase")' events.jsonl
# Count records in a JSONL file (no need to parse the whole thing)
wc -l events.jsonl
Streaming JSONL from an HTTP API
JSONL is the format of choice for streaming API responses — the server writes one JSON object per line as data becomes available, and the client reads and processes each line as it arrives without waiting for the complete response. Many AI APIs (including LLM completion endpoints) use a conceptually similar incremental format, where data arrives as a stream of JSON objects rather than a single document. The pattern for consuming them in the browser is the same: buffer chunks, split on newlines, parse each line.
// Consume a JSONL streaming response in the browser
const response = await fetch('/api/stream');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop(); // keep incomplete last line
for (const line of lines) {
if (line.trim()) {
const record = JSON.parse(line);
console.log('Received:', record);
}
}
}
Where JSONL is the standard
- Machine learning datasets: Hugging Face datasets default to JSONL for text data; fine-tuning datasets for LLMs (OpenAI, Anthropic) are submitted as JSONL files
- Application logs: structured logging libraries (Winston, Pino, Python structlog) can output JSONL for log aggregators like Loki, Datadog, and CloudWatch
- ETL pipelines: Apache Spark and Apache Flink read JSONL natively; each record is processed independently without shuffling the whole dataset
- BigQuery: Google’s newline-delimited JSON import format is JSONL
- Elasticsearch bulk API: the
_bulkendpoint expects JSONL — alternating action lines and document lines - Streaming APIs: OpenAI, Anthropic, and most LLM streaming endpoints deliver server-sent JSONL chunks
When to stick with JSON
- REST API responses — JSON is expected and tooling (Postman, browser DevTools) handles it natively. Use the JSON Formatter to inspect and pretty-print any API response.
- Config files —
package.json,tsconfig.json, settings files that humans read and edit - Small datasets that fit easily in memory
- When you need deep nesting, arrays of arrays, or complex document structure that does not map to a flat list of records. See JSON vs XML and YAML vs JSON for when a different format fits better.
- When the consumer expects a single valid JSON document (most SDKs and libraries). Validate the structure with the JSON Validator before sending.
Error resilience
One underrated advantage of JSONL is error isolation. If a JSON array file has a syntax error near the start — a stray comma, a missing quote — the entire file fails to parse. Nothing is recoverable.
In a JSONL file, a bad line only corrupts that one record. A parser that skips lines with json.JSONDecodeError can recover and process the remaining millions of records. For long-running log files written by multiple processes, this resilience is practically important.
Frequently Asked Questions
What is JSONL (JSON Lines)?
JSONL (JSON Lines), also called NDJSON (Newline-Delimited JSON), is a text format where each line contains a single, complete, valid JSON value — typically a JSON object. Files use the .jsonl or .ndjson extension. JSONL is not valid JSON itself; it is a separate streaming-friendly format that happens to use JSON syntax for each line.
Why use JSONL instead of a JSON array?
JSON arrays must be fully loaded into memory before any record can be read. A 10 GB JSON array requires ~10 GB of memory. JSONL lets you read and process one line at a time — constant memory regardless of file size. JSONL also supports appending without rewriting the file: open in append mode, write one line. With a JSON array, you must parse, modify, and rewrite the whole file.
What is the difference between JSONL and NDJSON?
JSONL and NDJSON are the same format with different names. Both store one JSON value per line separated by newlines. NDJSON stands for Newline-Delimited JSON. The .jsonl and .ndjson extensions are interchangeable. The JSONL specification and the NDJSON specification describe functionally identical formats.
Can I use the JSON formatter with JSONL files?
Not directly — JSONL is not valid JSON, so a standard JSON formatter will reject the whole file. To inspect a JSONL file, paste a single line from the file into the JSON Formatter. Each line is independently valid JSON and will format and validate correctly.
Is JSONL supported by big data tools?
Yes. JSONL is natively supported by Apache Spark (read via spark.read.json()), BigQuery (newline-delimited JSON import), Elasticsearch (bulk API), Hugging Face datasets, and jq which works line by line on JSONL streams without modification.
How do I convert a JSON array to JSONL?
With Python: import json; [open('out.jsonl','a').write(json.dumps(r)+'\n') for r in json.load(open('in.json'))]. With jq: jq -c '.[]' data.json > data.jsonl. The -c flag outputs compact single-line JSON; .[] iterates over each array element.
Need to inspect a JSONL record?
Paste any single line from a .jsonl file into the formatter — each line is valid JSON and will pretty-print cleanly.