Streaming JSON & NDJSON0%

Streaming JSON & NDJSON

Beginner12 min read•Updated: Jul 11, 2026
Study Materials

Streaming JSON & NDJSON in Modern JavaScript

Standard JSON.parse() requires the entire response payload to be fully downloaded into memory before parsing begins. For huge datasets (e.g. 500MB log exports, live analytics feeds, financial order books), buffering the whole file in RAM causes browser tabs to crash with Out of Memory errors. Streaming JSON and NDJSON (Newline Delimited JSON) allow records to be processed item-by-item as chunks arrive over the wire.


1. What is NDJSON (Newline Delimited JSON)?

NDJSON (also known as JSON Lines or .jsonl) stores each JSON record on its own line separated by a newline character \n:

Output
{"id": 1, "event": "PAGE_VIEW", "url": "/home", "timestamp": 1710000000}
{"id": 2, "event": "BUTTON_CLICK", "button": "signup", "timestamp": 1710000005}
{"id": 3, "event": "FORM_SUBMIT", "form": "register", "timestamp": 1710000010}

Why NDJSON is Superior for Streaming:

  • No enclosing square brackets [ or trailing commas ,.
  • Each line can be passed directly to JSON.parse(line) immediately as it arrives!

2. Parsing NDJSON Streams with ReadableStream

JavaScript
async function consumeNdjsonStream(url, onRecordReceived) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let leftoverBuffer = '';
 
while (true) {
const { value, done } = await reader.read();
if (done) break;
 
// Decode incoming binary chunk into string and append to leftover buffer
leftoverBuffer += decoder.decode(value, { stream: true });
 
// Split buffer by newlines
const lines = leftoverBuffer.split('\n');
 
// The last element may be incomplete! Keep it in the buffer for next chunk
leftoverBuffer = lines.pop();
 
// Process all fully completed lines
for (const line of lines) {
const trimmed = line.trim();
if (trimmed) {
try {
const record = JSON.parse(trimmed);
onRecordReceived(record); // Stream to UI or store
} catch (e) {
console.error('Malformed NDJSON line:', trimmed);
}
}
}
}
 
// Process any remaining tail in the buffer
if (leftoverBuffer.trim()) {
onRecordReceived(JSON.parse(leftoverBuffer.trim()));
}
}
Output
Network Stream (Byte Chunks)
├── Chunk 1: '{"id":1}\n{"id":' ──► Process record 1; Buffer '{"id":'
└── Chunk 2: '2}\n{"id":3}\n' ──► Complete record 2; Process record 3

3. High-Performance Transform Streams

In modern browsers supporting TransformStream and TextDecoderStream, you can pipe streams with declarative backpressure:

JavaScript
async function modernStream(url) {
const response = await fetch(url);
const stream = response.body
.pipeThrough(new TextDecoderStream())
.pipeThrough(new LineSplitterStream());
 
for await (const line of stream) {
const json = JSON.parse(line);
renderRowToTable(json);
}
}

Interactive Quiz

Practice Quiz

Question 1
What is the defining characteristic of NDJSON (Newline Delimited JSON)?
Question 2
Why does JSON.parse() fail when dealing with a 500MB continuous data stream?
Question 3
Why is leftoverBuffer = lines.pop() used when splitting streaming chunks by newline?
Question 4
Which standard browser API converts raw Uint8Array binary stream chunks into text strings while handling split multi-byte characters?
Question 5
What is the memory footprint of streaming 10 million NDJSON records compared to standard JSON.parse?
Next Lesson

Critical Rendering Path & Reflow/Repaint

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext