Introduction
If you work with data, you know JSON. But you may have also encountered JSONL (JSON Lines) or NDJSON (Newline-Delimited JSON) and wondered: are they the same thing? What's the difference, and when should you use each format? This guide breaks down the key differences, trade-offs, and real-world use cases for each format so you can make the right choice for your project.
What is JSON?
JSON (JavaScript Object Notation) is a text-based data format that stores structured data as key-value pairs, arrays, and nested objects. A JSON file contains a single root value - typically an object or array.
{
"users": [
{"id": 1, "name": "Alice", "role": "engineer"},
{"id": 2, "name": "Bob", "role": "designer"},
{"id": 3, "name": "Carol", "role": "manager"}
]
}JSON is the standard for APIs, configuration files, and data exchange. Almost every programming language has built-in JSON parsing. But it has a limitation: the entire document must be loaded into memory to parse it.
What is JSONL (JSON Lines)?
JSONL (also called NDJSON - Newline-Delimited JSON) is a format where each line in the file is a separate, valid JSON object. There are no commas between objects, no wrapping array, and no trailing commas. Each line is independent.
{"id": 1, "name": "Alice", "role": "engineer"}
{"id": 2, "name": "Bob", "role": "designer"}
{"id": 3, "name": "Carol", "role": "manager"}JSONL is not a replacement for JSON - it's a different format for different use cases. It's particularly popular in data engineering, logging, and machine learning pipelines.
JSONL vs NDJSON: Same Thing?
For practical purposes, JSONL and NDJSON are the same format. Both use newline-delimited JSON objects. The difference is purely naming:
- JSONL (jsonlines.org) - the original specification, uses
.jsonlfile extension - NDJSON (ndjson.org) - a community specification, uses
.ndjsonfile extension
Both follow the same rules: one JSON object per line, UTF-8 encoding, newline (\n) as delimiter. Most tools that support one also support the other.
Side-by-Side Comparison
| Feature | JSON | JSONL / NDJSON |
|---|---|---|
| Structure | Single root value (object/array) | One JSON object per line |
| Streaming | Must load entire file | Line-by-line streaming |
| Memory usage | O(n) - entire file in memory | O(1) per record - constant |
| Append data | Must rewrite entire file | Just append a new line |
| Error recovery | One error breaks entire parse | Skip bad lines, continue |
| Parallel processing | Difficult (nested structure) | Easy (split by lines) |
| File extension | .json | .jsonl or .ndjson |
| API responses | Standard format | Server-Sent Events, streaming |
| Human readability | High (with formatting) | Medium (one object per line) |
When to Use JSON
- API request/response payloads - REST APIs, GraphQL responses
- Configuration files - package.json, tsconfig.json, .eslintrc
- Small to medium datasets - data that fits comfortably in memory
- Nested, hierarchical data - when you need parent-child relationships
- Single-record responses - fetching one user, one product, etc.
When to Use JSONL / NDJSON
- Log files - each log entry is a JSON object on its own line
- Machine learning datasets - training data, embeddings, feature vectors
- Data pipelines - ETL processes, Kafka messages, event streams
- Large datasets - millions of records that can't fit in memory
- Append-only data - audit logs, event stores, time-series data
- Parallel processing - split file across workers, each processes a chunk of lines
- Server-Sent Events (SSE) - streaming JSON data to browsers
Code Examples
Reading JSON in Node.js
// Must load entire file into memory
const fs = require('fs');
const data = JSON.parse(fs.readFileSync('data.json', 'utf8'));
data.users.forEach(user => console.log(user.name));Reading JSONL in Node.js (Streaming)
// Stream line by line - constant memory usage
const readline = require('readline');
const fs = require('fs');
const rl = readline.createInterface({
input: fs.createReadStream('data.jsonl'),
});
rl.on('line', (line) => {
const record = JSON.parse(line);
console.log(record.name);
});Converting JSON to JSONL
// Convert a JSON array to JSONL
const data = require('./data.json');
const jsonl = data.users
.map(user => JSON.stringify(user))
.join('\n');
require('fs').writeFileSync('data.jsonl', jsonl);Converting JSONL to JSON
// Convert JSONL back to a JSON array
const fs = require('fs');
const lines = fs.readFileSync('data.jsonl', 'utf8')
.trim()
.split('\n');
const users = lines.map(line => JSON.parse(line));
fs.writeFileSync('data.json', JSON.stringify({ users }, null, 2));Performance Comparison
For a dataset of 1 million user records (~200 bytes each, ~200MB total):
| Operation | JSON | JSONL |
|---|---|---|
| Parse entire file | ~2s, 400MB RAM | ~1.5s, ~1MB RAM (streaming) |
| Append 1 record | Rewrite entire file | Append 1 line (O(1)) |
| Process in parallel | Complex splitting | Split by line count |
| Recover from corruption | Entire file lost | Only bad lines lost |
Key Takeaways
- Use JSON for APIs, configs, and small datasets that fit in memory
- Use JSONL/NDJSON for logs, large datasets, streaming, and append-only data
- JSONL and NDJSON are effectively the same format with different names
- JSONL's streaming capability makes it ideal for data pipelines and big data processing
- Converting between formats is straightforward with any programming language