A network chunk is not a complete JSON message. When you stream AI output, one object may arrive in several pieces and several objects may arrive together.
What you will build
Write and test a newline-delimited JSON parser that handles JSON objects split across network chunks.
Before you start
Node.js 18 or later for the executable fixture. The parser also works with text chunks produced by a browser TextDecoderStream.
1. Create ndjson.cjs
function createNdjsonParser(onValue) {
let pending = "";
function parseLine(line) {
if (line.trim()) onValue(JSON.parse(line));
}
return {
push(chunk) {
pending += chunk;
if (pending.length > 1024 * 1024) throw new Error("Buffer limit exceeded");
let end;
while ((end = pending.indexOf("\n")) !== -1) {
const line = pending.slice(0, end);
pending = pending.slice(end + 1);
parseLine(line);
}
},
finish() {
parseLine(pending);
pending = "";
}
};
}
const tokens = [];
const parser = createNdjsonParser(value => {
if (value.error) throw new Error(String(value.error));
if (typeof value.response === "string") tokens.push(value.response);
});
parser.push('{"response":"Hel');
parser.push('lo"}\n{"response":" world"}\n');
parser.push('{"done":true}');
parser.finish();
if (tokens.join("") !== "Hello world") throw new Error("Incorrect output");
console.log(tokens.join(""));
2. Run the example
node ndjson.cjsExpected result
Hello world is printed even though the first JSON object was split across two input chunks.
How it works
The parser keeps incomplete text between calls. It only invokes JSON.parse after locating a newline, then consumes a final non-empty line when finish runs. Blank lines are ignored.
This is an NDJSON parser, not a universal AI streaming parser. An SSE endpoint uses a different framing protocol, often with data: prefixes. Match the parser to the documented endpoint before connecting it to a real service.
For fetch integration, check response.ok before reading the body. Decode bytes with TextDecoderStream or a streaming TextDecoder so a multi-byte character split across byte chunks is preserved. Feed the decoded strings to push and call finish at end-of-stream.
The buffer ceiling keeps the small demo from accumulating an unlimited unterminated line. Production readers should also bound total output and support cancellation. Render generated content with textContent unless you apply a reviewed HTML sanitizer.
Test the edge cases
- Feed one character at a time and compare the output.
- Append a malformed JSON line and confirm the parser throws.
- Test CRLF endings and a last line without a newline.
Keep learning
Browse related tutorials. Reference: official documentation.
Reviewed 7 November 2026. AI-dependent examples require your own local model; generated text varies.