Description
When a non-objectMode Readable stream is used as a value in an object passed to JsonStreamStringify, the resulting JSON is invalid. The opening quote (") for the stream content is placed before the object key separator (,"key":) instead of after it.
This was introduced in v3.1.0 (the complete rewrite) and affects all versions from 3.1.0 through 3.1.6.
We faced this issue in a pipeline of our project and used GitHub Copilot to find and fix the Issue.
Minimal Reproduction
const { PassThrough } = require('node:stream');
const { JsonStreamStringify } = require('json-stream-stringify');
async function test() {
const stream = new PassThrough();
const obj = { key: 'value', data: stream };
const jss = new JsonStreamStringify(obj);
setImmediate(() => {
stream.write(Buffer.from('hello'));
stream.end();
});
const chunks = [];
for await (const chunk of jss) {
chunks.push(chunk.toString());
}
const result = chunks.join('');
console.log('Output:', result);
try {
JSON.parse(result);
console.log('Valid JSON');
} catch (e) {
console.log('INVALID JSON:', e.message);
}
}
test();
Expected output:
Output: {"key":"value","data":"hello"}
Valid JSON
Actual output (v3.1.6):
Output: {"key":"value"","data":hello"}
INVALID JSON: Expected ',' or '}' after property value in JSON at position 14 (line 1 column 15)
Root Cause
The bug is in the _push method in src/JsonStreamStringify.ts:
private _push(data) {
const out = (this.objectItem ? this.objectItem.write() : '') + data;
if (this.prePush && out.length) {
this.buffer += this.prePush; // ← prePush is placed BEFORE objectItem prefix + data
this.prePush = undefined;
}
this.buffer += out;
When setReadableStringItem sets this.prePush = '"', and then _push is called:
objectItem.write() returns the key prefix (e.g. ,"data":)
- These are concatenated into
out = ',"data":hello'
prePush (") is appended to buffer before out
Result: buffer += '"' then buffer += ',"data":hello' → ..."value"","data":hello"
Suggested Fix
Separate the object prefix from data, and insert prePush between them:
private _push(data) {
const prefix = this.objectItem ? this.objectItem.write() : '';
if (this.prePush && (prefix.length || data.length)) {
this.buffer += prefix + this.prePush;
this.prePush = undefined;
} else {
this.buffer += prefix;
}
this.buffer += data;
This ensures the key prefix (,"data":) is emitted first, then the opening quote ("), then the stream data.
Notes
- ReadableString as the first property in an object is unaffected (no
objectItem prefix to misorder)
ReadableObject values are unaffected (they use _push('[') directly, not prePush)
- Tested on Node.js v24.x
Affected versions
3.1.0 – 3.1.6 (all versions since the v3.1.0 rewrite)
Description
When a non-objectMode
Readablestream is used as a value in an object passed toJsonStreamStringify, the resulting JSON is invalid. The opening quote (") for the stream content is placed before the object key separator (,"key":) instead of after it.This was introduced in v3.1.0 (the complete rewrite) and affects all versions from 3.1.0 through 3.1.6.
We faced this issue in a pipeline of our project and used GitHub Copilot to find and fix the Issue.
Minimal Reproduction
Expected output:
Actual output (v3.1.6):
Root Cause
The bug is in the
_pushmethod insrc/JsonStreamStringify.ts:When
setReadableStringItemsetsthis.prePush = '"', and then_pushis called:objectItem.write()returns the key prefix (e.g.,"data":)out = ',"data":hello'prePush(") is appended to buffer beforeoutResult:
buffer += '"'thenbuffer += ',"data":hello'→..."value"","data":hello"Suggested Fix
Separate the object prefix from data, and insert
prePushbetween them:This ensures the key prefix (
,"data":) is emitted first, then the opening quote ("), then the stream data.Notes
objectItemprefix to misorder)ReadableObjectvalues are unaffected (they use_push('[')directly, notprePush)Affected versions
3.1.0 – 3.1.6 (all versions since the v3.1.0 rewrite)