JSON vs JSONL: When to Use Each Format
JSON arrays suit APIs and config files. JSON Lines fits logs, streaming, and large exports. Compare structure, parsing, and what breaks when a line is bad.
By Rehan Baqir
JSON is one tree. JSONL is one JSON value per line. JSON Formatter pretty-prints a normal JSON document. It will not turn a multi-gigabyte JSONL log into one array you can load at once, and JSON.parse on the whole JSONL file fails because the file is not one document. Use a JSON array or object for APIs, config, and files people edit in git. Use JSONL when you need to stream, append, or skip a bad line without throwing away the rest.
A 4 GB file that crashed every parser in a pipeline can still be a classic JSON array: { on the first line, } on the last, and millions of records in between. The same data as JSONL can stream through in batches without an out-of-memory error.
Both formats follow RFC 8259 for each value. The difference is packaging.
Standard JSON: one document, one tree
A JSON file or API response is typically one value:
{
"users": [
{ "id": 1, "name": "Ada" },
{ "id": 2, "name": "Grace" }
],
"total": 2
}
Or a top-level array:
[
{ "id": 1, "name": "Ada" },
{ "id": 2, "name": "Grace" }
]
Properties:
- A single parse loads the entire structure
- Nested hierarchy can express relationships directly
- This is the usual shape for REST APIs, config files, and package manifests
- Pretty-printing helps a person review it. Use JSON Formatter
Limits:
- The file should be complete and valid before a normal parse starts. Streaming parsers exist, and they are more complicated.
- One syntax error breaks the whole document
- A large array uses memory in proportion to the file size
JSONL: one JSON value per line
JSON Lines (.jsonl, .ndjson) puts independent JSON values on separate lines:
{"id": 1, "name": "Ada"}
{"id": 2, "name": "Grace"}
{"id": 3, "name": "Linus"}
There is no wrapping array and no comma between lines. Newline-delimited objects are the whole format.
Properties:
- Stream-friendly: read a line, parse it, process it, discard it
- Append a record by adding a line (log rotation, incremental export)
- A corrupt line does not have to invalidate the entire file
- Parallel work: split the file by line ranges across workers
Limits:
- There is no standard place for file-level metadata. A common workaround is a header object on the first line, or a sidecar file.
- The file is not valid input to a single
JSON.parse() - Minified one-liners are hard to read by eye
Side-by-side comparison
| Aspect | JSON (array/document) | JSONL | |--------|----------------------|-------| | Top-level structure | Object or array | Sequence of values | | Streaming | Harder | Native | | Append records | Rewrite file or use JSON Patch | Append lines | | Error isolation | One error fails all | Bad line skippable | | API commonality | Very common | Niche (streaming exports) | | Memory for 1M records | Often entire tree | One record at a time | | Schema metadata | Natural in root object | Convention-based |
When JSON wins
REST API responses, where clients expect { "data": [...], "meta": {...} }.
Configuration files such as package.json and tsconfig.json, which are one document.
Small datasets, under a few MB, where simplicity beats streaming.
Nested relationships, including graph-like data with cross-references.
Human editing, including formatted JSON in a git diff.
Validate the structure with JSON Validator before deploy.
When JSONL wins
Application logs: one event per line, shipped to Elasticsearch, BigQuery, or Splunk.
ML training data: millions of labeled examples, streamed into PyTorch or TensorFlow loaders.
Database exports: COPY TO style dumps and CDC streams.
ETL pipelines, where map-reduce workers each take a range of lines.
Incremental sync, where you append new records and leave history in place.
Large API exports, when a vendor offers "download all records" as .jsonl to avoid a timeout.
Parsing JSONL in practice
A typical loop:
for each line in file:
line = strip whitespace
if line is empty: continue
try:
record = JSON.parse(line)
process(record)
catch:
log bad line number, continue or abort based on policy
Libraries and tools:
- Python: read line by line,
json.loads(line) - Node: a
readlineinterface plusJSON.parse jq:jq -c .for compact output;while read line; do echo "$line" | jq .; done
Never call JSON.parse(entireFile) on JSONL.
Converting between formats
JSON array to JSONL:
const arr = JSON.parse(fs.readFileSync("data.json"));
arr.forEach(obj => console.log(JSON.stringify(obj)));
JSONL to a JSON array:
const lines = fs.readFileSync("data.jsonl", "utf8").trim().split("\n");
const arr = lines.map(line => JSON.parse(line));
For a huge file, stream both directions. Do not load the array into memory.
Pretty-print a converted JSON file with JSON Formatter when the export is small enough.
Common JSONL mistakes
Trailing commas between lines. JSONL is not a JSON array. Each line parses alone, so a comma in front of { on the next line fails.
A multi-line JSON object. One logical record split across lines breaks a line-based parser. Keep each record on a single minified line, or use a different format.
Mixed formats. A file that starts as a [ array and switches to JSONL in the middle.
A UTF-8 BOM on the first line. It breaks the first record. Strip the BOM on read.
An unescaped newline inside a string. It is uncommon and fatal. Strings must escape \n.
See Common JSON formatting errors for pitfalls the two formats share.
MIME types and tooling
| Format | Common Content-Type |
|--------|---------------------|
| JSON | application/json |
| JSONL | application/x-ndjson, application/jsonlines |
GitHub recognizes .jsonl. Some tools call it NDJSON (Newline Delimited JSON). Same idea.
Compression considerations
Gzip works on both. JSONL compresses well when keys repeat across lines, which feels columnar. Some pipelines archive .jsonl.gz.
Pretty-printed JSON with extra whitespace compresses worse. Minify before gzip if you are storing it.
Schema and validation
JSON Schema validates a single document. For JSONL:
- Validate each line against the same schema
- Or put a schema version on the first line:
{"_schema": 2}
JSON Validator helps you spot a bad line while you are developing.
APIs streaming JSONL
Some endpoints return:
HTTP/1.1 200 OK
Content-Type: application/x-ndjson
{"id":1,"status":"pending"}
{"id":2,"status":"complete"}
The client reads the body as a stream and parses line by line. That fits a long-running job that reports progress.
Most CRUD APIs still return a single JSON object. Do not assume JSONL support without documentation.
Choosing for your next project
Ask:
- Will the file exceed available RAM? Choose JSONL.
- Do consumers need random access to a nested structure? Choose a JSON document.
- Will you append records over time? Choose JSONL.
- Is this a public API contract? Choose JSON, unless streaming is documented.
- Do people edit this in git? Choose formatted JSON.
For logs and exports, JSONL is the usual pick. For APIs and config, JSON is.
Related articles
- JSON Formatting Guide, pretty-print and validate standard JSON
- Common JSON Formatting Errors, syntax pitfalls in both formats
- Case Conversion for API Data Cleanup, normalize field names in exports
Limitations
Edge cases, platform limits, and regulatory rules vary. Test on a sample file and keep the original source archived. This comparison covers how you read, write, and recover. It does not pick a parser library for you.
Conclusion
JSON packages data as one tree, which fits APIs, configs, and files people edit. JSONL packages data as one value per line, which fits logs, streams, and datasets that do not fit in memory. The syntax rules per value are the same. The container is different. Pick from how you will read, write, and recover from a bad record.
Frequently Asked Questions
Common questions answered to help you get the most from this tool.