json-stream-es
Version:
A streaming JSON parser/stringifier using web streams.
1,130 lines (1,118 loc) • 40.9 kB
JavaScript
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