UNPKG

4.26 kBPlain TextView Raw
1// deno-lint-ignore-file no-explicit-any no-prototype-builtins
2
3// Modernized version of Stefan Goessner's original JSON Path implementation.
4// Copyright (c) 2007 Stefan Goessner (goessner.net)
5// Licensed under the MIT license.
6
7// TODO: refactor to avoid string splitting/joining
8
9export function* trace<T = any>(expr: string, val: unknown, path: string): IterableIterator<[string, T]> {
10 if (expr) {
11 const [loc, ...rest] = expr.split(";");
12 const x = rest.join(";");
13
14 if (val !== null && typeof val === 'object' && loc in val) {
15 yield* trace(x, (<any>val)[loc], path + ";" + loc);
16 }
17 else if (loc === "*") {
18 for (const [m, _l, v, p] of walk(loc, val, path)) {
19 yield* trace(m + ";" + x, v, p)
20 }
21 }
22 else if (loc === "..") {
23 yield* trace(x, val, path);
24 for (const [m, _l, v, p] of walk(loc, val, path)) {
25 if (typeof (<any>v)[m] === "object")
26 yield* trace("..;" + x, (<any>v)[m], p + ";" + m);
27 }
28 }
29 else if (/,/.test(loc)) { // [name1,name2,...]
30 for (let s = loc.split(/'?,'?/), i = 0, n = s.length; i < n; i++)
31 yield* trace(s[i] + ";" + x, val, path);
32 }
33 else if (/^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/.test(loc)) { // [start:end:step] slice syntax
34 yield* slice(loc, x, val, path);
35 }
36 }
37 else yield [path, val as T]
38}
39
40function* slice<T>(loc: string, expr: string, val: unknown, path: string): IterableIterator<[string, T]> {
41 if (val instanceof Array) {
42 const len = val.length;
43 let start = 0, end = len, step = 1;
44 loc.replace(/^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/g, (_$0, $1, $2, $3) => {
45 start = parseInt($1 || start);
46 end = parseInt($2 || end);
47 step = parseInt($3 || step);
48 return ''
49 });
50 start = (start < 0) ? Math.max(0, start + len) : Math.min(len, start);
51 end = (end < 0) ? Math.max(0, end + len) : Math.min(len, end);
52 for (let i = start; i < end; i += step)
53 yield* trace(i + ";" + expr, val, path);
54 }
55}
56
57function* walk(loc: string, val: unknown, path: string) {
58 if (val instanceof Array) {
59 for (let i = 0, n = val.length; i < n; i++)
60 if (i in val)
61 yield [i, loc, val, path] as const
62 }
63 else if (typeof val === "object") {
64 for (const m in val)
65 if (val.hasOwnProperty(m))
66 yield [m, loc, val, path] as const
67 }
68}
69
70export function normalize(expr: string) {
71 const subX: string[] = [];
72 if (!expr.startsWith('$')) expr = '$' + expr
73 return expr
74 .replace(/[\['](\??\(.*?\))[\]']/g, (_$0, $1) => { return "[#" + (subX.push($1) - 1) + "]"; })
75 .replace(/'?\.'?|\['?/g, ";")
76 .replace(/;;;|;;/g, ";..;")
77 .replace(/;$|'?\]|'$/g, "")
78 .replace(/#([0-9]+)/g, (_$0, $1) => { return subX[$1]; });
79}
80
81// FIXME: avoid repeated split/join/regex.test
82export function match(expr: string, path: string): boolean {
83 if (expr && path) {
84 const [loc, ...restLoc] = expr.split(";");
85 const [val, ...restVal] = path.split(";");
86 const exprRest = restLoc.join(";");
87 const pathRest = restVal.join(';')
88
89 if (loc === val) {
90 return match(exprRest, pathRest)
91 }
92 else if (loc === "*") {
93 return match(exprRest, pathRest)
94 }
95 else if (loc === "..") {
96 return match(exprRest, path) || match("..;" + exprRest, pathRest);
97 }
98 else if (/,/.test(loc)) { // [name1,name2,...]
99 if (loc.split(/'?,'?/).some(v => v === val)) return match(exprRest, pathRest)
100 else return false
101 }
102 else if (/^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/.test(loc)) { // [start:end:step] slice syntax
103 let start = 0, end = Number.MAX_SAFE_INTEGER, step = 1;
104 loc.replace(/^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/g, (_$0, $1, $2, $3) => {
105 start = parseInt($1 || start);
106 end = parseInt($2 || end);
107 step = parseInt($3 || step);
108 return ''
109 });
110 const idx = Number(val)
111 if (start < 0 || end < 0 || step < 0)
112 throw TypeError('Negative numbers not supported. Can\'t know length ahead of time when stream parsing');
113 if (idx >= start && idx < end && start + idx % step === 0) return match(exprRest, pathRest)
114 else return false
115 }
116 }
117 else if (!expr && !path) return true
118 return false;
119}