NDJSON Complete Guide: Newline Delimited JSON Explained
A comprehensive guide to Newline Delimited JSON (NDJSON). Learn the specification, MIME type, how to read and write NDJSON in Python, Node.js, and the command line, use it in streaming HTTP APIs, and understand its relationship with JSONL.
Last updated: February 2026
What Is NDJSON?
NDJSON stands for Newline Delimited JSON. It is a text-based data format where each line contains exactly one valid JSON value, separated by a newline character (\n). The format was designed for streaming and processing large datasets without loading the entire file into memory. Unlike a standard JSON file that wraps everything in a single array or object, NDJSON lets you read, write, and process records one at a time.
The NDJSON specification is hosted at github.com/ndjson/ndjson-spec. It was created to formalize a pattern that had already become common in log shipping, data pipelines, and HTTP streaming APIs. Each line is self-contained: if one line contains invalid JSON, other lines can still be parsed successfully. This makes NDJSON fault-tolerant and well-suited for append-only workflows such as application logs, event streams, and incremental data exports.
{"id":1,"event":"page_view","url":"/home"}{"id":2,"event":"click","url":"/pricing"}{"id":3,"event":"signup","url":"/register"}
The NDJSON Specification
The official NDJSON specification at github.com/ndjson/ndjson-spec is intentionally minimal. It defines a simple convention for serializing multiple JSON values in a single text stream. The core rules are straightforward:
The specification explicitly avoids adding headers, metadata, or schema information to the format. This keeps NDJSON as simple as possible and compatible with standard Unix text processing tools. Any valid JSON value is allowed on each line, though in practice most NDJSON files contain JSON objects with a consistent set of keys.
- Each line MUST contain exactly one valid JSON value (object, array, string, number, boolean, or null).
- Lines are separated by the newline character '\n' (U+000A). The carriage return '\r' (U+000D) may appear before '\n' but is not required.
- A trailing newline after the last JSON value is allowed but not required.
- Each JSON value must not contain unescaped newline characters within the value itself.
Because the rules are minimal, NDJSON is easy to generate from any programming language that has a JSON serializer. Simply serialize each record, append a newline, and write it to the output. No closing brackets, no trailing commas, no enclosing array structure to worry about.
NDJSON vs JSON vs JSONL
NDJSON, JSON, and JSONL each have a different structure. Standard JSON encodes a single value (usually an array or object). JSONL and NDJSON both store one JSON value per line, and they are functionally identical to each other. The table below highlights the key differences between all three formats.
| Feature | JSON | JSONL | NDJSON |
|---|---|---|---|
| Full Name | JavaScript Object Notation | JSON Lines | Newline Delimited JSON |
| File Extension | .json | .jsonl | .ndjson |
| MIME Type | application/json | application/jsonl (unofficial) | application/x-ndjson |
| Specification | RFC 8259 (IETF standard) | jsonlines.org (community) | github.com/ndjson/ndjson-spec (community) |
| Line Delimiter | N/A (single value) | \n (newline) | \n (newline) |
| Trailing Newline | N/A | Recommended | Optional |
| Stream-Friendly | No (must parse entire document) | Yes (line-by-line) | Yes (line-by-line) |
NDJSON MIME Type: application/x-ndjson
The registered MIME type for NDJSON is application/x-ndjson. This content type is used in HTTP headers to indicate that the response body contains newline-delimited JSON data. Many streaming APIs, including the GitHub API, Docker Registry API, and Elasticsearch bulk API, use this MIME type to deliver NDJSON responses.
Content-Type: application/x-ndjson# Example: curl a streaming APIcurl -H "Accept: application/x-ndjson" https://api.example.com/events/stream# Example: Express.js responseres.setHeader('Content-Type', 'application/x-ndjson');res.write(JSON.stringify(record) + '\n');
Some APIs also accept application/json-seq (RFC 7464) or text/plain as alternatives. However, application/x-ndjson is the most widely adopted MIME type for newline-delimited JSON streams. When building a new API that streams JSON records, use application/x-ndjson for maximum compatibility.
Reading and Writing NDJSON
Working with NDJSON is straightforward in any language that supports JSON. Below are practical examples for Python, Node.js, and the command line.
Python's built-in json module handles NDJSON naturally. Read lines from a file, parse each one with json.loads, and write with json.dumps. For large files, this line-by-line approach uses constant memory.
guide-ndjson-complete-guide.ndjsonGuide.readWrite.python.code
In Node.js, use the readline module with fs.createReadStream to parse NDJSON files efficiently. The stream processes one line at a time, keeping memory usage low regardless of file size.
import { createReadStream, writeFileSync } from 'node:fs';import { createInterface } from 'node:readline';// Read NDJSONasync function readNdjson(filePath) {const records = [];const rl = createInterface({input: createReadStream(filePath, 'utf-8'),crlfDelay: Infinity,});for await (const line of rl) {const trimmed = line.trim();if (trimmed) records.push(JSON.parse(trimmed));}return records;}// Write NDJSONconst data = [{ id: 1, event: 'page_view' },{ id: 2, event: 'click' },];const ndjson = data.map(r => JSON.stringify(r)).join('\n') + '\n';writeFileSync('output.ndjson', ndjson, 'utf-8');
The jq command-line tool natively understands NDJSON input. Use the --slurp flag to collect all records into an array, or process each record individually without any flag.
# Print each record (jq reads NDJSON by default)jq '.' data.ndjson# Filter records where event is "click"jq 'select(.event == "click")' data.ndjson# Extract specific fieldsjq {id, url} data.ndjson# Count total recordsjq -s 'length' data.ndjson# Convert NDJSON to a JSON arrayjq -s '.' data.ndjson > data.json# Convert a JSON array back to NDJSONjq -c '.[]' data.json > data.ndjson
NDJSON in HTTP Streaming APIs
NDJSON is the de facto standard for HTTP streaming APIs that deliver real-time data. When a server sends an NDJSON response, the client can begin processing the first record as soon as it arrives, without waiting for the entire response to complete. This is faster and more memory-efficient than returning a large JSON array.
Popular services that use NDJSON streaming include Docker Registry (image layer events), Elasticsearch (bulk operations), Apache CouchDB (changes feed), and many modern event-driven APIs. The pattern works well with Server-Sent Events alternatives, real-time log tailing, and progressive data loading in web applications.
import express from 'express';const app = express();app.get('/api/events/stream', (req, res) => {res.setHeader('Content-Type', 'application/x-ndjson');res.setHeader('Transfer-Encoding', 'chunked');// Simulate streaming eventslet id = 0;const interval = setInterval(() => {id++;const event = {id,type: 'heartbeat',timestamp: new Date().toISOString(),};res.write(JSON.stringify(event) + '\n');if (id >= 100) {clearInterval(interval);res.end();}}, 100);req.on('close', () => clearInterval(interval));});app.listen(3000);
async function* readNdjsonStream(url) {const response = await fetch(url, {headers: { 'Accept': 'application/x-ndjson' },});const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();let buffer = '';while (true) {const { done, value } = await reader.read();if (done) break;buffer += value;const lines = buffer.split('\n');buffer = lines.pop();for (const line of lines) {if (line.trim()) yield JSON.parse(line);}}if (buffer.trim()) yield JSON.parse(buffer);}// Usagefor await (const event of readNdjsonStream('/api/events/stream')) {console.log('Received:', event);}
NDJSON Ecosystem Tools
A growing ecosystem of command-line tools and libraries supports NDJSON natively. These tools let you filter, transform, and analyze NDJSON data without writing custom code.
jq
Essentialjq is the most popular command-line JSON processor. It reads NDJSON by default (one JSON value per line) and supports filtering, mapping, grouping, and reformatting. Use jq -c for compact output and jq -s to slurp all records into an array.
ndjson-cli
CLIndjson-cli is a collection of Unix-style commands for manipulating NDJSON streams: ndjson-filter, ndjson-map, ndjson-reduce, ndjson-sort, and ndjson-join. Each command reads from stdin and writes to stdout, making them composable with pipes.
ndjson (npm)
Node.jsThe ndjson npm package provides streaming NDJSON parsers and serializers for Node.js. It exposes ndjson.parse() and ndjson.stringify() transform streams that integrate directly into Node.js stream pipelines for high-throughput data processing.
NDJSON and JSONL: Interoperability
NDJSON and JSONL (JSON Lines) are functionally identical formats. Both store one JSON value per line, separated by newline characters. A file that is valid NDJSON is also valid JSONL, and vice versa. You can rename a .ndjson file to .jsonl (or the other way around) without changing a single byte of content, and every tool that reads one format will read the other.
The only differences are cosmetic: NDJSON comes from github.com/ndjson/ndjson-spec and uses the .ndjson extension with the application/x-ndjson MIME type, while JSONL comes from jsonlines.org and uses the .jsonl extension. In practice, most developers treat the two names as synonyms. If your project already uses JSONL, there is no need to migrate to NDJSON, and if a library says it supports NDJSON, it will work with your .jsonl files without any changes.
Try Our Free NDJSON/JSONL Tools
Work with NDJSON and JSONL files right in your browser. All processing happens locally, so your data stays private.