UNPKG

json-stream-es

Version:

A streaming JSON parser/stringifier using web streams.

1,130 lines (1,118 loc) 40.9 kB
var JsonChunkType = /* @__PURE__ */ ((JsonChunkType2) => { JsonChunkType2["WHITESPACE"] = "WHITESPACE"; JsonChunkType2["COMMA"] = "COMMA"; JsonChunkType2["COLON"] = "COLON"; JsonChunkType2["OBJECT_START"] = "OBJECT_START"; JsonChunkType2["OBJECT_END"] = "OBJECT_END"; JsonChunkType2["ARRAY_START"] = "ARRAY_START"; JsonChunkType2["ARRAY_END"] = "ARRAY_END"; JsonChunkType2["STRING_START"] = "STRING_START"; JsonChunkType2["STRING_CHUNK"] = "STRING_CHUNK"; JsonChunkType2["STRING_END"] = "STRING_END"; JsonChunkType2["NUMBER_VALUE"] = "NUMBER_VALUE"; JsonChunkType2["BOOLEAN_VALUE"] = "BOOLEAN_VALUE"; JsonChunkType2["NULL_VALUE"] = "NULL_VALUE"; return JsonChunkType2; })(JsonChunkType || {}); var StringRole = /* @__PURE__ */ ((StringRole2) => { StringRole2["KEY"] = "KEY"; StringRole2["VALUE"] = "VALUE"; return StringRole2; })(StringRole || {}); function whitespace(rawValue) { return { type: "WHITESPACE" /* WHITESPACE */, rawValue }; } function comma(rawValue = ",") { return { type: "COMMA" /* COMMA */, rawValue }; } function colon(rawValue = ":") { return { type: "COLON" /* COLON */, rawValue }; } function objectStart(rawValue = "{") { return { type: "OBJECT_START" /* OBJECT_START */, rawValue }; } function objectEnd(rawValue = "}") { return { type: "OBJECT_END" /* OBJECT_END */, rawValue }; } function arrayStart(rawValue = "[") { return { type: "ARRAY_START" /* ARRAY_START */, rawValue }; } function arrayEnd(rawValue = "]") { return { type: "ARRAY_END" /* ARRAY_END */, rawValue }; } function stringStart(role = "VALUE" /* VALUE */, rawValue = '"') { return { type: "STRING_START" /* STRING_START */, role, rawValue }; } function stringChunk(value, role = "VALUE" /* VALUE */, rawValue) { return { type: "STRING_CHUNK" /* STRING_CHUNK */, role, value, rawValue: rawValue ?? JSON.stringify(value).slice(1, -1) }; } function stringEnd(role = "VALUE" /* VALUE */, rawValue = '"') { return { type: "STRING_END" /* STRING_END */, role, rawValue }; } function numberValue(value, rawValue) { return { type: "NUMBER_VALUE" /* NUMBER_VALUE */, value, rawValue: rawValue ?? JSON.stringify(value) }; } function booleanValue(value, rawValue) { return { type: "BOOLEAN_VALUE" /* BOOLEAN_VALUE */, value, rawValue: rawValue ?? JSON.stringify(value) }; } function nullValue(rawValue = "null") { return { type: "NULL_VALUE" /* NULL_VALUE */, value: null, rawValue }; } async function* streamToIterable(stream) { const reader = stream.getReader(); try { while (true) { const { done, value } = await reader.read(); if (done) { return; } else { yield value; } } } finally { reader.cancel().catch(() => void 0); } } function iterableToSource(iterable) { const iterator = Symbol.asyncIterator in iterable ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator](); return { async pull(controller) { const { value, done } = await iterator.next(); if (done) { controller.close(); } else { controller.enqueue(value); } } }; } function iterableToStream(iterable, strategy) { return new ReadableStream(iterableToSource(iterable), strategy); } async function streamToArray(stream) { const reader = stream.getReader(); const result = []; while (true) { const { done, value } = await reader.read(); if (done) { return result; } else { result.push(value); } } } async function streamToString(stream) { return (await streamToArray(stream)).join(""); } function stringToStream(string) { return iterableToStream([string]); } function concatStreams(...streams) { const transform = new TransformStream(); (async () => { for (const stream of streams) { await (typeof stream === "function" ? stream() : stream).pipeTo(transform.writable, { preventClose: true }); } await transform.writable.close(); })().catch(async (err) => { await transform.writable.abort(err); }); return transform.readable; } class AbortHandlingTransformStream extends TransformStream { constructor(transformer, writableStrategy, readableStrategy) { const { abort, start, ...rest } = transformer ?? {}; let controller; super({ ...rest, start: (c) => { controller = c; start?.(c); } }, writableStrategy, readableStrategy); const writer = this.writable.getWriter(); const writable = new WritableStream({ write: (chunk) => writer.write(chunk), close: () => writer.close(), abort: async (reason) => { if (abort) { try { await abort(reason, controller); } catch (err) { await writer.abort(err); } } else { await writer.abort(reason); } } }); Object.defineProperty(this, "writable", { get: () => writable, configurable: true }); } } class AbstractTransformStream extends AbortHandlingTransformStream { constructor(writableStrategy, readableStrategy) { super({ transform: (chunk, controller) => { return this.transform(chunk, controller); }, flush: (controller) => { return this.flush(controller); }, abort: (reason, controller) => { return this.abort(reason, controller); } }, writableStrategy, readableStrategy); } flush(controller) { controller.terminate(); } abort(reason, controller) { controller.error(reason); } } class PipeableTransformStream extends TransformStream { constructor(transformReadable, writableStrategy, readableStrategy) { super({}, writableStrategy); const readable = transformReadable(this.readable).pipeThrough(new TransformStream({}, void 0, readableStrategy)); Object.defineProperty(this, "readable", { get: () => readable }); } } function arrayStartsWith(array, startsWith) { return array.length >= startsWith.length && startsWith.every((v, i) => array[i] === v); } class JsonDeserializer extends AbstractTransformStream { state = { type: "ROOT" /* ROOT */, value: void 0, path: [] }; constructor() { super(); } handleValueEnd(controller) { if (this.state.type === "ROOT" /* ROOT */) { if (this.state.value !== void 0) { controller.enqueue({ value: this.state.value, path: this.state.path }); } this.state.value = void 0; } else if (this.state.type === "OBJECT_PROPERTY" /* OBJECT_PROPERTY */) { if (this.state.value !== void 0) { this.state.object[this.state.key] = this.state.value; } this.state.key = ""; this.state.value = void 0; } else if (this.state.type === "ARRAY_ITEM" /* ARRAY_ITEM */) { if (this.state.value !== void 0) { this.state.array.push(this.state.value); } this.state.value = void 0; } } transform(chunk, controller) { if (chunk.type === JsonChunkType.NUMBER_VALUE || chunk.type === JsonChunkType.BOOLEAN_VALUE || chunk.type === JsonChunkType.NULL_VALUE) { this.state.value = chunk.value; if (this.state.type === "ROOT" /* ROOT */) { this.state.path = chunk.path; } this.handleValueEnd(controller); } else if (chunk.type === JsonChunkType.STRING_START && chunk.role === StringRole.VALUE) { this.state.value = ""; if (this.state.type === "ROOT" /* ROOT */) { this.state.path = chunk.path; } } else if (chunk.type === JsonChunkType.STRING_CHUNK && chunk.role === StringRole.VALUE) { this.state.value += chunk.value; } else if (chunk.type === JsonChunkType.STRING_END && chunk.role === StringRole.VALUE) { this.handleValueEnd(controller); } else if (chunk.type === JsonChunkType.ARRAY_START) { this.state.value = []; if (this.state.type === "ROOT" /* ROOT */) { this.state.path = chunk.path; } this.state = { type: "ARRAY_ITEM" /* ARRAY_ITEM */, array: this.state.value, value: void 0, parent: this.state }; } else if (chunk.type === JsonChunkType.ARRAY_END && this.state.type === "ARRAY_ITEM" /* ARRAY_ITEM */) { this.state = this.state.parent; this.handleValueEnd(controller); } else if (chunk.type === JsonChunkType.OBJECT_START) { this.state.value = {}; if (this.state.type === "ROOT" /* ROOT */) { this.state.path = chunk.path; } this.state = { type: "OBJECT_PROPERTY" /* OBJECT_PROPERTY */, object: this.state.value, key: "", value: void 0, parent: this.state }; } else if (chunk.type === JsonChunkType.OBJECT_END && this.state.type === "OBJECT_PROPERTY" /* OBJECT_PROPERTY */) { this.state = this.state.parent; this.handleValueEnd(controller); } else if (chunk.type === JsonChunkType.STRING_CHUNK && chunk.role === StringRole.KEY && this.state.type === "OBJECT_PROPERTY" /* OBJECT_PROPERTY */) { this.state.key += chunk.value; } } } async function deserializeJsonValue(stream) { const reader = stream.pipeThrough(new JsonDeserializer()).getReader(); const { value, done: done1 } = await reader.read(); if (done1) { throw new Error("The stream did not contain any values."); } const { done: done2 } = await reader.read(); if (!done2) { reader.cancel().catch(() => void 0); throw new Error("The stream contained more than one value."); } return value.value; } const VALUE_START_ALLOWED = ["start" /* START */, "object_after_colon" /* OBJECT_AFTER_COLON */, "array_after_start" /* ARRAY_AFTER_START */, "array_after_comma" /* ARRAY_AFTER_COMMA */]; const VALUE_START_ALLOWED_MULTI = [...VALUE_START_ALLOWED, "end" /* END */]; const KEY_START_ALLOWED = ["object_after_start" /* OBJECT_AFTER_START */, "object_after_comma" /* OBJECT_AFTER_COMMA */]; const WHITESPACE_ALLOWED = [ "start" /* START */, "object_after_start" /* OBJECT_AFTER_START */, "object_after_key" /* OBJECT_AFTER_KEY */, "object_after_colon" /* OBJECT_AFTER_COLON */, "object_after_value" /* OBJECT_AFTER_VALUE */, "object_after_comma" /* OBJECT_AFTER_COMMA */, "array_after_start" /* ARRAY_AFTER_START */, "array_after_value" /* ARRAY_AFTER_VALUE */, "array_after_comma" /* ARRAY_AFTER_COMMA */, "end" /* END */ ]; function isState(state, types) { return types.includes(state.type); } function getStateAfterValue(stateBeforeValue) { if (isState(stateBeforeValue, ["start" /* START */, "end" /* END */])) { return { ...stateBeforeValue, type: "end" /* END */ }; } else if (stateBeforeValue.type === "object_after_colon" /* OBJECT_AFTER_COLON */) { return { ...stateBeforeValue, type: "object_after_value" /* OBJECT_AFTER_VALUE */ }; } else if (isState(stateBeforeValue, ["array_after_start" /* ARRAY_AFTER_START */, "array_after_comma" /* ARRAY_AFTER_COMMA */])) { return { ...stateBeforeValue, type: "array_after_value" /* ARRAY_AFTER_VALUE */ }; } else if (isState(stateBeforeValue, ["object_after_start" /* OBJECT_AFTER_START */, "object_after_comma" /* OBJECT_AFTER_COMMA */])) { return { ...stateBeforeValue, type: "object_after_key" /* OBJECT_AFTER_KEY */ }; } else { throw new Error(`Invalid value state ${stateBeforeValue.type}.`); } } class UnexpectedCharError extends Error { constructor(context) { super(`Unexpected character "${context.char}" at position ${context.position}.`); } } class PrematureEndError extends Error { constructor() { super("Premature end of JSON stream."); } } const STRING_ESCAPE_CHARS = { '"': '"', "\\": "\\", "/": "/", "b": "\b", "f": "\f", "n": "\n", "r": "\r", "t": " " }; const WHITESPACE_CHARS = [" ", " ", "\n", "\r"]; const RS_CHARS = [""]; const NUMBER_CHARS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; const HEX_NUMBER_CHARS = [...NUMBER_CHARS, "a", "b", "c", "d", "e", "f", "A", "B", "C", "D", "E", "F"]; const BOOLEAN_OR_NULL = { false: false, true: true, null: null }; const BOOLEAN_OR_NULL_FIRST_CHARS = Object.keys(BOOLEAN_OR_NULL).map((k) => k[0]); const BOOLEAN_OR_NULL_CHARS = [...new Set(Object.keys(BOOLEAN_OR_NULL).flatMap((k) => [...k]))]; class JsonParser extends AbstractTransformStream { constructor(options = {}) { super(); this.options = options; } state = { type: "start" /* START */ }; lengthBeforeCurrentChunk = 0; /** * Checks whether a token that doesn't have an explicit end character (that is: numbers and whitespaces) has ended, and if * so, update the state and emit the appropriate chunks. * @param char The next character on the stream. Is used to check whether the current token ends (for example, a number is ended * by a non-number character). If undefined, the stream is assumed to have ended, so the current token must always end. */ checkValueEnd(controller, char) { if (this.state.type === "whitespace" /* WHITESPACE */ && (char == null || !WHITESPACE_CHARS.includes(char))) { if (this.state.rawValue.length > 0) { controller.enqueue(whitespace(this.state.rawValue)); } this.state = this.state.parentState; } if (this.state.type === "number_digits" /* NUMBER_DIGITS */ && (char == null || ![...NUMBER_CHARS, ".", "e", "E"].includes(char)) || this.state.type === "number_decimal_digits" /* NUMBER_DECIMAL_DIGITS */ && (char == null || ![...NUMBER_CHARS, "e", "E"].includes(char)) || this.state.type === "number_e_digits" /* NUMBER_E_DIGITS */ && (char == null || !NUMBER_CHARS.includes(char))) { controller.enqueue(numberValue(Number(this.state.rawValue), this.state.rawValue)); this.state = getStateAfterValue(this.state.parentState); } } /** * Handle a single character piped into the stream. */ handleChar(controller, context) { const char = context.char; this.checkValueEnd(controller, char); if (char === "{" && isState(this.state, this.options.multi ? VALUE_START_ALLOWED_MULTI : VALUE_START_ALLOWED)) { controller.enqueue(objectStart(char)); this.state = { type: "object_after_start" /* OBJECT_AFTER_START */, parentState: this.state }; return; } if (char === "}" && isState(this.state, ["object_after_start" /* OBJECT_AFTER_START */, "object_after_value" /* OBJECT_AFTER_VALUE */])) { controller.enqueue(objectEnd(char)); this.state = getStateAfterValue(this.state.parentState); return; } if (char === ":" && isState(this.state, ["object_after_key" /* OBJECT_AFTER_KEY */])) { controller.enqueue(colon(char)); this.state = { type: "object_after_colon" /* OBJECT_AFTER_COLON */, parentState: this.state.parentState }; return; } if (char === "," && isState(this.state, ["object_after_value" /* OBJECT_AFTER_VALUE */])) { controller.enqueue(comma(char)); this.state = { type: "object_after_comma" /* OBJECT_AFTER_COMMA */, parentState: this.state.parentState }; return; } if (char === "[" && isState(this.state, this.options.multi ? VALUE_START_ALLOWED_MULTI : VALUE_START_ALLOWED)) { controller.enqueue(arrayStart(char)); this.state = { type: "array_after_start" /* ARRAY_AFTER_START */, parentState: this.state }; return; } if (char === "]" && isState(this.state, ["array_after_start" /* ARRAY_AFTER_START */, "array_after_value" /* ARRAY_AFTER_VALUE */])) { controller.enqueue(arrayEnd(char)); this.state = getStateAfterValue(this.state.parentState); return; } if (char === "," && isState(this.state, ["array_after_value" /* ARRAY_AFTER_VALUE */])) { controller.enqueue(comma(char)); this.state = { type: "array_after_comma" /* ARRAY_AFTER_COMMA */, parentState: this.state.parentState }; return; } if (BOOLEAN_OR_NULL_FIRST_CHARS.includes(char) && isState(this.state, this.options.multi ? VALUE_START_ALLOWED_MULTI : VALUE_START_ALLOWED)) { this.state = { type: "boolean_or_null" /* BOOLEAN_OR_NULL */, rawValue: char, parentState: this.state }; return; } if (BOOLEAN_OR_NULL_CHARS.includes(char) && this.state.type === "boolean_or_null" /* BOOLEAN_OR_NULL */) { const rawValue = `${this.state.rawValue}${char}`; for (const [key, value] of Object.entries(BOOLEAN_OR_NULL)) { if (rawValue === key) { if (typeof value === "boolean") { controller.enqueue(booleanValue(value, rawValue)); } else { controller.enqueue(nullValue(rawValue)); } this.state = getStateAfterValue(this.state.parentState); return; } if (key.startsWith(rawValue)) { this.state.rawValue = rawValue; return; } } } if (char === '"') { if (isState(this.state, this.options.multi ? VALUE_START_ALLOWED_MULTI : VALUE_START_ALLOWED)) { controller.enqueue(stringStart(StringRole.VALUE, char)); this.state = { type: "string" /* STRING */, value: "", rawValue: "", role: StringRole.VALUE, parentState: this.state }; return; } if (isState(this.state, KEY_START_ALLOWED)) { controller.enqueue(stringStart(StringRole.KEY, char)); this.state = { type: "string" /* STRING */, value: "", rawValue: "", role: StringRole.KEY, parentState: this.state }; return; } if (isState(this.state, ["string" /* STRING */])) { if (this.state.rawValue.length > 0) { controller.enqueue(stringChunk(this.state.value, this.state.role, this.state.rawValue)); } controller.enqueue(stringEnd(this.state.role, char)); this.state = getStateAfterValue(this.state.parentState); return; } } if (char === "\\" && isState(this.state, ["string" /* STRING */])) { this.state = { type: "string_after_backslash" /* STRING_AFTER_BACKSLASH */, rawValue: char, parentState: this.state }; return; } if (Object.prototype.hasOwnProperty.call(STRING_ESCAPE_CHARS, char) && isState(this.state, ["string_after_backslash" /* STRING_AFTER_BACKSLASH */])) { this.state = { ...this.state.parentState, value: `${this.state.parentState.value}${STRING_ESCAPE_CHARS[char]}`, rawValue: `${this.state.parentState.rawValue}${this.state.rawValue}${char}` }; return; } if (char === "u" && isState(this.state, ["string_after_backslash" /* STRING_AFTER_BACKSLASH */])) { this.state = { type: "string_after_backslash_u" /* STRING_AFTER_BACKSLASH_U */, value: "", rawValue: `${this.state.rawValue}${char}`, parentState: this.state.parentState }; return; } if (HEX_NUMBER_CHARS.includes(char) && isState(this.state, ["string_after_backslash_u" /* STRING_AFTER_BACKSLASH_U */])) { this.state.value += char; this.state.rawValue += char; if (this.state.value.length === 4) { this.state = { ...this.state.parentState, value: `${this.state.parentState.value}${String.fromCharCode(parseInt(this.state.value, 16))}`, rawValue: `${this.state.parentState.rawValue}${this.state.rawValue}` }; } return; } if (char.charCodeAt(0) >= 32 && isState(this.state, ["string" /* STRING */])) { this.state.value += char; this.state.rawValue += char; return; } if (char === "-" && isState(this.state, this.options.multi ? VALUE_START_ALLOWED_MULTI : VALUE_START_ALLOWED)) { this.state = { type: "number_minus" /* NUMBER_MINUS */, rawValue: char, parentState: this.state }; return; } if ((char === "-" || char === "+") && this.state.type === "number_e" /* NUMBER_E */) { this.state = { type: "number_e_plusminus" /* NUMBER_E_PLUSMINUS */, rawValue: `${this.state.rawValue}${char}`, parentState: this.state.parentState }; return; } if (char === "." && this.state.type === "number_digits" /* NUMBER_DIGITS */) { this.state = { type: "number_point" /* NUMBER_POINT */, rawValue: `${this.state.rawValue}${char}`, parentState: this.state.parentState }; return; } if ((char === "e" || char === "E") && isState(this.state, ["number_digits" /* NUMBER_DIGITS */, "number_decimal_digits" /* NUMBER_DECIMAL_DIGITS */])) { this.state = { type: "number_e" /* NUMBER_E */, rawValue: `${this.state.rawValue}${char}`, parentState: this.state.parentState }; return; } if (NUMBER_CHARS.includes(char)) { if (this.state.type === "number_minus" /* NUMBER_MINUS */) { this.state = { type: "number_digits" /* NUMBER_DIGITS */, rawValue: `${this.state.rawValue}${char}`, parentState: this.state.parentState }; return; } if (this.state.type === "number_point" /* NUMBER_POINT */) { this.state = { type: "number_decimal_digits" /* NUMBER_DECIMAL_DIGITS */, rawValue: `${this.state.rawValue}${char}`, parentState: this.state.parentState }; return; } if (isState(this.state, ["number_e" /* NUMBER_E */, "number_e_plusminus" /* NUMBER_E_PLUSMINUS */])) { this.state = { type: "number_e_digits" /* NUMBER_E_DIGITS */, rawValue: `${this.state.rawValue}${char}`, parentState: this.state.parentState }; return; } if (isState(this.state, ["number_digits" /* NUMBER_DIGITS */, "number_decimal_digits" /* NUMBER_DECIMAL_DIGITS */, "number_e_digits" /* NUMBER_E_DIGITS */])) { this.state.rawValue += char; return; } if (isState(this.state, this.options.multi ? VALUE_START_ALLOWED_MULTI : VALUE_START_ALLOWED)) { this.state = { type: "number_digits" /* NUMBER_DIGITS */, rawValue: char, parentState: this.state }; return; } } if (WHITESPACE_CHARS.includes(char) || this.options.multi && isState(this.state, ["start" /* START */, "end" /* END */]) && RS_CHARS.includes(char)) { if (this.state.type === "whitespace" /* WHITESPACE */) { this.state.rawValue += char; return; } if (isState(this.state, WHITESPACE_ALLOWED)) { this.state = { type: "whitespace" /* WHITESPACE */, rawValue: char, parentState: this.state }; return; } } throw new UnexpectedCharError(context); } /** * Called at the end of the transformation of a chunk. Should flush partial values where applicable, * in particular and incomplete strings or whitespaces can be emitted. */ handleChunkEnd(controller) { const stringState = this.state.type === "string" /* STRING */ ? this.state : isState(this.state, ["string_after_backslash" /* STRING_AFTER_BACKSLASH */, "string_after_backslash_u" /* STRING_AFTER_BACKSLASH_U */]) ? this.state.parentState : void 0; if (stringState) { controller.enqueue(stringChunk(stringState.value, stringState.role, stringState.rawValue)); stringState.rawValue = ""; stringState.value = ""; } if (this.state.type === "whitespace" /* WHITESPACE */ && this.state.rawValue.length > 0) { controller.enqueue(whitespace(this.state.rawValue)); this.state.rawValue = ""; } } /** * Transforms an incoming chunk. */ transform(chunk, controller) { for (let i = 0; i < chunk.length; i++) { this.handleChar(controller, { char: chunk[i], position: this.lengthBeforeCurrentChunk + i }); } this.lengthBeforeCurrentChunk += chunk.length; this.handleChunkEnd(controller); } /** * Called when the end of the incoming stream is reached. Checks that a complete value has been emitted. */ flush(controller) { this.checkValueEnd(controller, void 0); if (this.state.type !== "end" /* END */ && (!this.options.multi || this.state.type !== "start" /* START */)) { throw new PrematureEndError(); } controller.terminate(); } } function normalizeStream(iterable, symbol) { if (Symbol.asyncIterator in iterable || Symbol.iterator in iterable) { return Object.assign(iterableToStream(iterable), { [symbol]: true }); } else { return Object.assign(Object.create(iterable), { [symbol]: true }); } } const stringStreamSymbol = Symbol("stringStream"); function stringStream(stream) { return normalizeStream(stream, stringStreamSymbol); } function isStringStream(value) { return value && typeof value === "object" && !!value[stringStreamSymbol]; } const objectStreamSymbol = Symbol("objectStream"); function objectStream(obj) { return normalizeStream(obj, objectStreamSymbol); } function isObjectStream(value) { return value && typeof value === "object" && !!value[objectStreamSymbol]; } const arrayStreamSymbol = Symbol("arrayStream"); function arrayStream(obj) { return normalizeStream(obj, arrayStreamSymbol); } function isArrayStream(value) { return value && typeof value === "object" && !!value[arrayStreamSymbol]; } function normalizeSpace(space) { if (typeof space === "number") { return " ".repeat(space); } else if (typeof space === "string") { return space; } else { return ""; } } async function* serializeJson(value, space, spacePrefix = "", key = "") { const normalizedSpace = normalizeSpace(space); let val = await (typeof value === "function" && !("toJSON" in value) ? value() : value); val = val && "toJSON" in Object(val) ? Object(val).toJSON(key) : val; if (typeof val === "boolean" || typeof val === "object" && Object.prototype.toString.call(val) === "[object Boolean]") { yield booleanValue(Boolean(val)); } else if (typeof val === "number" || typeof val === "object" && Object.prototype.toString.call(val) === "[object Number]") { const num = Number(val); if (isFinite(num)) { yield numberValue(num); } else { yield nullValue(); } } else if (typeof val === "bigint" || typeof val === "object" && Object.prototype.toString.call(val) === "[object BigInt]") { yield numberValue(Number(val), String(val)); } else if (typeof val === "string" || typeof val === "object" && Object.prototype.toString.call(val) === "[object String]" || isStringStream(val)) { yield stringStart(); for await (const chunk of isStringStream(val) ? streamToIterable(val) : [String(val)]) { yield stringChunk(chunk); } yield stringEnd(); } else if ("isRawJSON" in JSON && JSON.isRawJSON(val)) { for await (const chunk of streamToIterable(stringToStream(val.rawJSON).pipeThrough(new JsonParser()))) { yield chunk; } } else if (Array.isArray(val) || isArrayStream(val)) { yield arrayStart(); let i = 0; for await (const v of isArrayStream(val) ? streamToIterable(val) : val) { if (i > 0) { yield comma(); } if (normalizedSpace) { yield whitespace(` ${spacePrefix}${normalizedSpace}`); } for await (const chunk of serializeJson(v, space, `${spacePrefix}${normalizedSpace}`, `${i}`)) { yield chunk; } i++; } if (i > 0 && normalizedSpace) { yield whitespace(` ${spacePrefix}`); } yield arrayEnd(); } else if (typeof val === "object" && val) { yield objectStart(); let first = true; for await (const [k, rawV] of isObjectStream(val) ? streamToIterable(val) : Object.entries(val)) { const v = await (typeof rawV === "function" ? rawV() : rawV); if (v === void 0 || typeof k === "symbol" || typeof v === "symbol") { continue; } if (first) { first = false; } else { yield comma(); } if (normalizedSpace) { yield whitespace(` ${spacePrefix}${normalizedSpace}`); } yield stringStart(StringRole.KEY); for await (const chunk of isStringStream(k) ? streamToIterable(k) : [`${k}`]) { yield stringChunk(chunk, StringRole.KEY); } yield stringEnd(StringRole.KEY); yield colon(); if (normalizedSpace) { yield whitespace(" "); } for await (const chunk of serializeJson(v, space, `${spacePrefix}${normalizedSpace}`, isStringStream(k) ? "" : k)) { yield chunk; } } if (!first && normalizedSpace) { yield whitespace(` ${spacePrefix}`); } yield objectEnd(); } else { yield nullValue(); } } class JsonSerializer extends AbstractTransformStream { constructor(space, options) { super(); this.space = space; this.options = options; } first = true; async transform(value, controller) { if (this.first) { if (this.options?.beforeFirst) { controller.enqueue(whitespace(this.options.beforeFirst)); } this.first = false; } else if (this.options?.delimiter !== "") { controller.enqueue(whitespace(this.options?.delimiter ?? "\n")); } for await (const chunk of serializeJson(value, this.space)) { controller.enqueue(chunk); } } flush(controller) { if (!this.first && this.options?.afterLast) { controller.enqueue(whitespace(this.options.afterLast)); } controller.terminate(); } } function serializeJsonValue(value, space) { const serializer = new JsonSerializer(space); const writer = serializer.writable.getWriter(); writer.write(value).catch(() => void 0); writer.close().catch(() => void 0); return serializer.readable; } class JsonStringifier extends AbstractTransformStream { constructor() { super(); } transform(chunk, controller) { controller.enqueue(chunk.rawValue); } flush(controller) { controller.terminate(); } } class JsonPathDetector extends AbstractTransformStream { stack = []; path = []; constructor() { super(); } transform(chunk, controller) { if (this.stack[this.stack.length - 1]?.state === "next") { this.stack[this.stack.length - 1].state = "active"; this.path.push(this.stack[this.stack.length - 1].key); } if (chunk.type === JsonChunkType.OBJECT_START) { this.stack.push({ type: "object", state: "pending", key: "" }); } else if (chunk.type === JsonChunkType.ARRAY_START) { this.stack.push({ type: "array", state: "next", key: 0 }); } else if (chunk.type === JsonChunkType.OBJECT_END || chunk.type === JsonChunkType.ARRAY_END) { if (this.stack.pop()?.state !== "pending") { this.path.pop(); } } else { const current = this.stack[this.stack.length - 1]; if (current?.type === "object") { if (chunk.type === JsonChunkType.STRING_CHUNK && chunk.role === StringRole.KEY) { current.key += chunk.value; } else if (chunk.type === JsonChunkType.COLON) { current.state = "next"; } else if (chunk.type === JsonChunkType.COMMA) { this.path.pop(); current.state = "pending"; current.key = ""; } } else if (current?.type === "array") { if (chunk.type === JsonChunkType.COMMA) { current.state = "next"; current.key++; this.path.pop(); } } } controller.enqueue({ ...chunk, path: [...this.path] }); } } function matchesJsonPathSelector(path, selector) { if (typeof selector === "function") { return selector(path); } else { return path.length === selector.length && selector.every((v, i) => { if (v === void 0) { return true; } else if (Array.isArray(v)) { return v.includes(path[i]); } else { return path[i] === v; } }); } } class JsonPathSelector extends AbstractTransformStream { constructor(selector) { super(); this.selector = selector; } currentPathPrefix = void 0; transform(chunk, controller) { if (this.currentPathPrefix && arrayStartsWith(chunk.path, this.currentPathPrefix)) { controller.enqueue(chunk); } else if (matchesJsonPathSelector(chunk.path, this.selector)) { this.currentPathPrefix = chunk.path; controller.enqueue(chunk); } else { this.currentPathPrefix = void 0; } } flush(controller) { controller.terminate(); } } class StreamSplitter extends TransformStream { constructor(options) { super({}); this.options = options; let chunkIdx = 0; const [mainInput, nestedInput] = this.readable.pipeThrough(new TransformStream({ transform: (chunk, controller) => { controller.enqueue([chunkIdx++, chunk]); } })).tee(); const main = new TransformStream({ transform: ([chunkIdx2, chunk], controller) => this.transformMain([chunkIdx2, chunk], controller) }); main.readable.tee = function() { return teeNestedStream(this); }; Object.defineProperty(this, "readable", { get: () => main.readable, configurable: true }); mainInput.pipeTo(main.writable).catch(() => void 0); const nested = new WritableStream({ write: ([chunkIdx2, chunk], controller) => this.handleNestedChunk([chunkIdx2, chunk]), close: () => this.handleNestedClose(), abort: (reason) => this.handleNestedAbort(reason) }); nestedInput.pipeTo(nested).catch(() => void 0); } lastChunkIdx = void 0; nestedStreams = {}; currentNestedStream = void 0; nestedWriters = {}; currentWriter = void 0; transformMain([chunkIdx, chunk], controller) { this.handleChunk([chunkIdx, chunk]); if (this.nestedStreams[chunkIdx]) { controller.enqueue(this.nestedStreams[chunkIdx]); delete this.nestedStreams[chunkIdx]; } } async handleNestedChunk([chunkIdx, chunk]) { this.handleChunk([chunkIdx, chunk]); if (this.nestedWriters[chunkIdx]) { if (this.currentWriter) { try { await this.currentWriter.close(); } catch { } } this.currentWriter = this.nestedWriters[chunkIdx]; delete this.nestedWriters[chunkIdx]; } if (this.currentWriter) { try { await this.currentWriter.write(chunk); } catch (err) { } } } async handleNestedClose() { await Promise.all([ ...Object.values(this.nestedWriters), ...this.currentWriter ? [this.currentWriter] : [] ].map((w) => w.close())); } async handleNestedAbort(reason) { await Promise.all([ ...Object.values(this.nestedWriters), ...this.currentWriter ? [this.currentWriter] : [] ].map((w) => w.abort(reason))); } handleChunk([chunkIdx, chunk]) { if (this.lastChunkIdx != null && chunkIdx <= this.lastChunkIdx) { return; } if (!this.currentNestedStream || !this.belongsToNestedStream(chunk, this.currentNestedStream)) { const nestedStream = new TransformStream(); this.nestedStreams[chunkIdx] = Object.assign(Object.create(nestedStream.readable), this.options.getNestedStreamProperties(chunk)); this.currentNestedStream = this.nestedStreams[chunkIdx]; this.nestedWriters[chunkIdx] = nestedStream.writable.getWriter(); } this.lastChunkIdx = chunkIdx; } belongsToNestedStream(chunk, stream) { if (this.options.belongsToNestedStream) { return this.options.belongsToNestedStream(chunk, stream); } else { const chunkProperties = this.options.getNestedStreamProperties(chunk); const [streamKeys, chunkKeys] = [Object.keys(stream), Object.keys(chunkProperties)]; return streamKeys.length === chunkKeys.length && streamKeys.every((k) => chunkKeys.includes(k) && stream[k] === chunkProperties[k]); } } } function teeNestedStream(stream) { const [stream1, stream2] = stream.pipeThrough(new TransformStream({ transform: (chunk, controller) => { const [nestedStream1, nestedStream2] = chunk.tee(); const nestedStreamProperties = Object.fromEntries(Object.entries(chunk)); controller.enqueue([ Object.assign(Object.create(nestedStream1), nestedStreamProperties), Object.assign(Object.create(nestedStream2), nestedStreamProperties) ]); } })).tee(); return [ stream1.pipeThrough(new TransformStream({ transform: (chunk, controller) => { controller.enqueue(chunk[0]); } })), stream2.pipeThrough(new TransformStream({ transform: (chunk, controller) => { controller.enqueue(chunk[1]); } })) ]; } class JsonPathStreamSplitter extends StreamSplitter { constructor() { super({ getNestedStreamProperties: (chunk) => ({ path: chunk.path }), belongsToNestedStream: (chunk, stream) => arrayStartsWith(chunk.path, stream.path) }); const readable = this.readable.pipeThrough(new TransformStream({ transform: (subStream, controller) => { controller.enqueue(Object.assign(subStream.pipeThrough(new TransformStream({ transform: (chunk, controller2) => { controller2.enqueue({ ...chunk, path: chunk.path.slice(subStream.path.length) }); } })), { path: subStream.path })); } })); Object.defineProperty(this, "readable", { get: () => readable, configurable: true }); } } function stringifyJsonStream(value, space) { return serializeJsonValue(value, space).pipeThrough(new JsonStringifier()); } function stringifyMultiJsonStream(space, options) { return new PipeableTransformStream((readable) => { return readable.pipeThrough(new JsonSerializer(space, options)).pipeThrough(new JsonStringifier()); }); } class ValueExtractor extends AbstractTransformStream { transform(chunk, controller) { controller.enqueue(chunk.value); } } function parseJsonStreamWithPaths(selector, options) { return new PipeableTransformStream((readable) => { let result = readable.pipeThrough(new JsonParser(options)).pipeThrough(new JsonPathDetector()); if (selector) { result = result.pipeThrough(new JsonPathSelector((path) => path.length > 0 && matchesJsonPathSelector(path.slice(0, -1), selector))); } return result.pipeThrough(new JsonDeserializer()); }); } function parseJsonStream(selector, options) { return new PipeableTransformStream((readable) => { let result = readable.pipeThrough(new JsonParser(options)); if (selector) { result = result.pipeThrough(new JsonPathDetector()).pipeThrough(new JsonPathSelector((path) => path.length > 0 && matchesJsonPathSelector(path.slice(0, -1), selector))); } return result.pipeThrough(new JsonDeserializer()).pipeThrough(new ValueExtractor()); }); } function parseNestedJsonStreamWithPaths(selector, options) { return new PipeableTransformStream((readable) => { return readable.pipeThrough(new JsonParser(options)).pipeThrough(new JsonPathDetector()).pipeThrough(new JsonPathSelector(selector)).pipeThrough(new JsonPathStreamSplitter()).pipeThrough(new TransformStream({ transform: (chunk, controller) => { controller.enqueue(Object.assign( chunk.pipeThrough(new JsonPathSelector([void 0])).pipeThrough(new JsonDeserializer()), { path: chunk.path } )); } })); }); } function parseNestedJsonStream(selector, options) { return new PipeableTransformStream((readable) => { return readable.pipeThrough(parseNestedJsonStreamWithPaths(selector, options)).pipeThrough(new TransformStream({ transform: (chunk, controller) => { controller.enqueue(Object.assign( chunk.pipeThrough(new ValueExtractor()), { path: chunk.path } )); } })); }); } export { AbortHandlingTransformStream, AbstractTransformStream, JsonChunkType, JsonDeserializer, JsonParser, JsonPathDetector, JsonPathSelector, JsonPathStreamSplitter, JsonSerializer, JsonStringifier, PipeableTransformStream, PrematureEndError, StreamSplitter, StringRole, UnexpectedCharError, arrayEnd, arrayStart, arrayStartsWith, arrayStream, booleanValue, colon, comma, concatStreams, deserializeJsonValue, isArrayStream, isObjectStream, isStringStream, iterableToSource, iterableToStream, matchesJsonPathSelector, nullValue, numberValue, objectEnd, objectStart, objectStream, parseJsonStream, parseJsonStreamWithPaths, parseNestedJsonStream, parseNestedJsonStreamWithPaths, serializeJsonValue, streamToArray, streamToIterable, streamToString, stringChunk, stringEnd, stringStart, stringStream, stringToStream, stringifyJsonStream, stringifyMultiJsonStream, whitespace }; //# sourceMappingURL=json-stream-es.mjs.map