UNPKG

@worker-tools/json-stream

Version:

Utilities for working with streaming JSON in Worker Runtimes such as Cloudflare Workers, Deno Deploy and Service Workers.

123 lines 3.96 kB
// deno-lint-ignore-file no-explicit-any no-empty import { asyncIterToStream } from 'whatwg-stream-to-async-iter'; export const isIterable = (x) => x != null && typeof x === 'object' && Symbol.iterator in x; export const isAsyncIterable = (x) => x != null && typeof x === 'object' && Symbol.asyncIterator in x; const isPromiseLike = (x) => x != null && typeof x === 'object' && 'then' in x && typeof x.then === 'function'; const isToJSON = (x) => x != null && typeof x === 'object' && 'toJSON' in x; const safeAdd = (seen, value) => { if (seen.has(value)) throw TypeError('Converting circular structure to JSON'); seen.add(value); }; const check = (v) => { if (v === undefined) return false; const type = typeof v; return type !== 'function' && type !== 'symbol'; }; // TODO: Add replacer // TODO: add formatting/spaces // TODO: concurrent objects/arrays /** * @deprecated Change name to something more descriptive!? */ export async function* jsonStringifyGenerator(value, seen = new WeakSet()) { if (isAsyncIterable(value)) { yield '['; safeAdd(seen, value); let first = true; for await (const v of value) { if (!first) yield ','; else first = false; yield* jsonStringifyGenerator(v, seen); } seen.delete(value); yield ']'; } else if (isPromiseLike(value)) { const v = await value; if (check(v)) { safeAdd(seen, value); yield* jsonStringifyGenerator(v, seen); seen.delete(value); } } else if (isToJSON(value)) { const v = JSON.stringify(value); if (check(v)) yield v; } else if (Array.isArray(value)) { yield '['; safeAdd(seen, value); let first = true; for (const v of value) { if (!first) yield ','; else first = false; yield* jsonStringifyGenerator(v, seen); } seen.delete(value); yield ']'; } else if (value != null && typeof value === 'object') { yield '{'; safeAdd(seen, value); let first = true; for (const [k, v] of Object.entries(value)) { if (check(v)) { const generator = jsonStringifyGenerator(v, seen); const peek = await generator.next(); if (check(peek.value)) { if (!first) yield ','; else first = false; yield `${JSON.stringify(k)}:`; yield peek.value; yield* generator; } } } seen.delete(value); yield '}'; } else { yield check(value) ? JSON.stringify(value) : 'null'; } } /** * @deprecated Change name to something more descriptive!? */ export function jsonStringifyStream(value) { return asyncIterToStream(jsonStringifyGenerator(value)); } export class JSONStringifyReadable extends ReadableStream { constructor(value) { let iterator; super({ start() { iterator = jsonStringifyGenerator(value)[Symbol.asyncIterator](); }, async pull(controller) { // console.log('stringify', controller.desiredSize) const { value, done } = await iterator.next(); if (!done) controller.enqueue(value); else controller.close(); }, async cancel(reason) { var _a; try { await ((_a = iterator.throw) === null || _a === void 0 ? void 0 : _a.call(iterator, reason)); } catch { } }, }); } } //# sourceMappingURL=json-stringify.js.map