@worker-tools/json-stream
Version:
Utilities for working with streaming JSON in Worker Runtimes such as Cloudflare Workers, Deno Deploy and Service Workers.
124 lines • 4.74 kB
JavaScript
// deno-lint-ignore-file no-explicit-any no-prototype-builtins
Object.defineProperty(exports, "__esModule", { value: true });
exports.match = exports.normalize = exports.trace = void 0;
// Modernized version of Stefan Goessner's original JSON Path implementation.
// Copyright (c) 2007 Stefan Goessner (goessner.net)
// Licensed under the MIT license.
// TODO: refactor to avoid string splitting/joining
function* trace(expr, val, path) {
if (expr) {
const [loc, ...rest] = expr.split(";");
const x = rest.join(";");
if (val !== null && typeof val === 'object' && loc in val) {
yield* trace(x, val[loc], path + ";" + loc);
}
else if (loc === "*") {
for (const [m, _l, v, p] of walk(loc, val, path)) {
yield* trace(m + ";" + x, v, p);
}
}
else if (loc === "..") {
yield* trace(x, val, path);
for (const [m, _l, v, p] of walk(loc, val, path)) {
if (typeof v[m] === "object")
yield* trace("..;" + x, v[m], p + ";" + m);
}
}
else if (/,/.test(loc)) { // [name1,name2,...]
for (let s = loc.split(/'?,'?/), i = 0, n = s.length; i < n; i++)
yield* trace(s[i] + ";" + x, val, path);
}
else if (/^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/.test(loc)) { // [start:end:step] slice syntax
yield* slice(loc, x, val, path);
}
}
else
yield [path, val];
}
exports.trace = trace;
function* slice(loc, expr, val, path) {
if (val instanceof Array) {
const len = val.length;
let start = 0, end = len, step = 1;
loc.replace(/^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/g, (_$0, $1, $2, $3) => {
start = parseInt($1 || start);
end = parseInt($2 || end);
step = parseInt($3 || step);
return '';
});
start = (start < 0) ? Math.max(0, start + len) : Math.min(len, start);
end = (end < 0) ? Math.max(0, end + len) : Math.min(len, end);
for (let i = start; i < end; i += step)
yield* trace(i + ";" + expr, val, path);
}
}
function* walk(loc, val, path) {
if (val instanceof Array) {
for (let i = 0, n = val.length; i < n; i++)
if (i in val)
yield [i, loc, val, path];
}
else if (typeof val === "object") {
for (const m in val)
if (val.hasOwnProperty(m))
yield [m, loc, val, path];
}
}
function normalize(expr) {
const subX = [];
if (!expr.startsWith('$'))
expr = '$' + expr;
return expr
.replace(/[\['](\??\(.*?\))[\]']/g, (_$0, $1) => { return "[#" + (subX.push($1) - 1) + "]"; })
.replace(/'?\.'?|\['?/g, ";")
.replace(/;;;|;;/g, ";..;")
.replace(/;$|'?\]|'$/g, "")
.replace(/#([0-9]+)/g, (_$0, $1) => { return subX[$1]; });
}
exports.normalize = normalize;
// FIXME: avoid repeated split/join/regex.test
function match(expr, path) {
if (expr && path) {
const [loc, ...restLoc] = expr.split(";");
const [val, ...restVal] = path.split(";");
const exprRest = restLoc.join(";");
const pathRest = restVal.join(';');
if (loc === val) {
return match(exprRest, pathRest);
}
else if (loc === "*") {
return match(exprRest, pathRest);
}
else if (loc === "..") {
return match(exprRest, path) || match("..;" + exprRest, pathRest);
}
else if (/,/.test(loc)) { // [name1,name2,...]
if (loc.split(/'?,'?/).some(v => v === val))
return match(exprRest, pathRest);
else
return false;
}
else if (/^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/.test(loc)) { // [start:end:step] slice syntax
let start = 0, end = Number.MAX_SAFE_INTEGER, step = 1;
loc.replace(/^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/g, (_$0, $1, $2, $3) => {
start = parseInt($1 || start);
end = parseInt($2 || end);
step = parseInt($3 || step);
return '';
});
const idx = Number(val);
if (start < 0 || end < 0 || step < 0)
throw TypeError('Negative numbers not supported. Can\'t know length ahead of time when stream parsing');
if (idx >= start && idx < end && start + idx % step === 0)
return match(exprRest, pathRest);
else
return false;
}
}
else if (!expr && !path)
return true;
return false;
}
exports.match = match;
//# sourceMappingURL=json-path.js.map
;