-
Notifications
You must be signed in to change notification settings - Fork 13
Description
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 beforeout
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
objectItemprefix to misorder) ReadableObjectvalues are unaffected (they use_push('[')directly, notprePush)- Tested on Node.js v24.x
Affected versions
3.1.0 – 3.1.6 (all versions since the v3.1.0 rewrite)