UNPKG

json-web-streams

Version:

Streaming JSON parser built on top of the Web Streams API, so it works in web browsers, Node.js, and many other environments

142 lines (140 loc) 4.95 kB
import { JSONParseStreamRaw } from "./JSONParseStreamRaw.js"; import { jsonPathToPathArray } from "./jsonPathToPathArray.js"; //#region src/JSONParseStream.ts const isEqual = (x, y) => { if (!y) return false; if (x.type === "wildcard") { if (y.mode === "ARRAY") return true; else if (y.mode === "OBJECT") return true; } else if (y.mode === "OBJECT" && x.value === y.key) return true; return false; }; var JSONParseStream = class extends TransformStream { _parser; constructor(jsonPaths, options) { let parser; let minPathArrayLength = Infinity; let maxPathArrayLength = -Infinity; const jsonPathInfos = jsonPaths.map((row) => { let key; let path; let schema; if (typeof row === "string") path = row; else { key = row.key; path = row.path; schema = row.schema; } const pathArray = jsonPathToPathArray(path); let wildcardIndexes; for (const [i, component] of pathArray.entries()) if (component.type === "wildcard") { if (wildcardIndexes === void 0) wildcardIndexes = []; wildcardIndexes.push(i); } const matches = pathArray.length === 0 ? "yes" : "unknown"; if (pathArray.length > maxPathArrayLength) maxPathArrayLength = pathArray.length; if (pathArray.length < minPathArrayLength) minPathArrayLength = pathArray.length; return { key, matches, path, pathArray, validate: schema?.["~standard"].validate, wildcardIndexes }; }); const jsonPathInfosThatMatch = new Set(jsonPathInfos.filter((info) => info.matches === "yes")); const updateMatches = (type) => { for (const info of jsonPathInfos) { const pathArray = info.pathArray; if ((info.matches === "unknown" && type === "push" || info.matches !== "unknown" && info.matches !== "noBeforeEnd" && type === "key") && parser.stack.length === pathArray.length) { let pathMatches = "yes"; for (let j = 0; j < pathArray.length; j++) { let stackComponent = parser.stack[j + 1]; if (!stackComponent) { if (type === "key") stackComponent = parser; else if (pathArray[j].type === "wildcard") { pathMatches = "yes"; break; } } if (!isEqual(pathArray[j], stackComponent)) { if (j < pathArray.length - 1) pathMatches = "noBeforeEnd"; else pathMatches = "noAtEnd"; break; } } info.matches = pathMatches; if (pathMatches === "yes") jsonPathInfosThatMatch.add(info); else jsonPathInfosThatMatch.delete(info); } } }; super({ start(controller) { parser = new JSONParseStreamRaw({ multi: options?.multi, onKey: (stackLength) => { if (stackLength <= maxPathArrayLength && stackLength >= minPathArrayLength) updateMatches("key"); }, onPop: (stackLength) => { if (stackLength < maxPathArrayLength && stackLength >= minPathArrayLength - 1) { for (const info of jsonPathInfos) if (info.matches !== "unknown" && stackLength < info.pathArray.length) { info.matches = "unknown"; jsonPathInfosThatMatch.delete(info); } } }, onPush: (stackLength) => { if (stackLength <= maxPathArrayLength && stackLength >= minPathArrayLength) updateMatches("push"); }, onValue: (value) => { let keep = false; for (const { key, path, pathArray, validate, wildcardIndexes } of jsonPathInfosThatMatch) if (parser.stack.length === pathArray.length) { let valueToEmit; if (validate) { const result = validate(value); if (result instanceof Promise) throw new TypeError("Schema validation must be synchronous"); if (result.issues) throw new Error(JSON.stringify(result.issues, null, 2)); valueToEmit = result.value; } else valueToEmit = value; let wildcardKeys; if (wildcardIndexes) for (const index of wildcardIndexes) { const stackComponent = parser.stack[index + 1] ?? parser; if (stackComponent.mode === "OBJECT" && stackComponent.key !== void 0) { if (!wildcardKeys) wildcardKeys = []; wildcardKeys.push(stackComponent.key); } } if (wildcardKeys) controller.enqueue({ key: key ?? path, value: valueToEmit, wildcardKeys }); else controller.enqueue({ key: key ?? path, value: valueToEmit }); } else keep = true; if (keep) return; const type = typeof value; if (!(type === "string" || type === "number" || type === "boolean" || value === null)) { for (const row of parser.stack) row.value = void 0; if (typeof parser.value === "object" && parser.value !== null && parser.key !== void 0) parser.value[parser.key] = void 0; } } }); }, transform(chunk) { parser.write(chunk); }, flush(controller) { parser.checkEnd(); controller.terminate(); } }); this._parser = parser; } }; //#endregion export { JSONParseStream };