donobu 3.19.2 → 3.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/utils/JsonPath.d.ts +30 -0
- package/dist/esm/utils/JsonPath.d.ts.map +1 -0
- package/dist/esm/utils/JsonPath.js +622 -0
- package/dist/esm/utils/JsonPath.js.map +1 -0
- package/dist/esm/utils/TemplateInterpolator.d.ts.map +1 -1
- package/dist/esm/utils/TemplateInterpolator.js +2 -72
- package/dist/esm/utils/TemplateInterpolator.js.map +1 -1
- package/dist/utils/JsonPath.d.ts +30 -0
- package/dist/utils/JsonPath.d.ts.map +1 -0
- package/dist/utils/JsonPath.js +622 -0
- package/dist/utils/JsonPath.js.map +1 -0
- package/dist/utils/TemplateInterpolator.d.ts.map +1 -1
- package/dist/utils/TemplateInterpolator.js +2 -72
- package/dist/utils/TemplateInterpolator.js.map +1 -1
- package/package.json +7 -12
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A minimal, safe JSONPath evaluator that replaces the `jsonpath` npm package.
|
|
3
|
+
*
|
|
4
|
+
* The `jsonpath` library (v1.2.1) has CVE GHSA-87r5-mp6g-5w5j due to its use
|
|
5
|
+
* of `eval()` for script expressions. This implementation covers the subset of
|
|
6
|
+
* JSONPath used by this project with NO dynamic code execution.
|
|
7
|
+
*
|
|
8
|
+
* Supported features:
|
|
9
|
+
* - Property access: $.user.name
|
|
10
|
+
* - Array indexing (positive & negative): $.calls[0], $.calls[-1]
|
|
11
|
+
* - Slice notation: $.calls[-2:], $.calls[1:], $.calls[0:2]
|
|
12
|
+
* - Filter expressions: $.books[?(@.price < 10)]
|
|
13
|
+
* - Logical AND in filters: [?(@.a < 5 && @.b == "x")]
|
|
14
|
+
* - Property existence filters: [?(@.isbn)]
|
|
15
|
+
* - Filter + index: $.books[?(@.price < 10)][0]
|
|
16
|
+
* - Recursive descent: $..author
|
|
17
|
+
* - Wildcards: $[*], $..books[*].title
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Evaluate a JSONPath expression against a data object.
|
|
21
|
+
*
|
|
22
|
+
* Drop-in replacement for `jsonpath.query()` covering the subset of JSONPath
|
|
23
|
+
* used by this project. Uses no `eval()` or dynamic code execution.
|
|
24
|
+
*
|
|
25
|
+
* @param data - The data to query
|
|
26
|
+
* @param expression - A JSONPath expression (must start with $)
|
|
27
|
+
* @returns An array of matching values
|
|
28
|
+
*/
|
|
29
|
+
export declare function queryJsonPath(data: unknown, expression: string): unknown[];
|
|
30
|
+
//# sourceMappingURL=JsonPath.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JsonPath.d.ts","sourceRoot":"","sources":["../../../src/utils/JsonPath.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAssBH;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,EAAE,CAI1E"}
|
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* A minimal, safe JSONPath evaluator that replaces the `jsonpath` npm package.
|
|
4
|
+
*
|
|
5
|
+
* The `jsonpath` library (v1.2.1) has CVE GHSA-87r5-mp6g-5w5j due to its use
|
|
6
|
+
* of `eval()` for script expressions. This implementation covers the subset of
|
|
7
|
+
* JSONPath used by this project with NO dynamic code execution.
|
|
8
|
+
*
|
|
9
|
+
* Supported features:
|
|
10
|
+
* - Property access: $.user.name
|
|
11
|
+
* - Array indexing (positive & negative): $.calls[0], $.calls[-1]
|
|
12
|
+
* - Slice notation: $.calls[-2:], $.calls[1:], $.calls[0:2]
|
|
13
|
+
* - Filter expressions: $.books[?(@.price < 10)]
|
|
14
|
+
* - Logical AND in filters: [?(@.a < 5 && @.b == "x")]
|
|
15
|
+
* - Property existence filters: [?(@.isbn)]
|
|
16
|
+
* - Filter + index: $.books[?(@.price < 10)][0]
|
|
17
|
+
* - Recursive descent: $..author
|
|
18
|
+
* - Wildcards: $[*], $..books[*].title
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.queryJsonPath = queryJsonPath;
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Tokenizer
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
function tokenize(expression) {
|
|
26
|
+
const tokens = [];
|
|
27
|
+
let i = 0;
|
|
28
|
+
if (expression[i] !== '$') {
|
|
29
|
+
throw new Error(`JSONPath must start with $, got: ${expression}`);
|
|
30
|
+
}
|
|
31
|
+
tokens.push({ type: 'root' });
|
|
32
|
+
i++;
|
|
33
|
+
while (i < expression.length) {
|
|
34
|
+
if (expression[i] === '.') {
|
|
35
|
+
if (expression[i + 1] === '.') {
|
|
36
|
+
tokens.push({ type: 'dotdot' });
|
|
37
|
+
i += 2;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
tokens.push({ type: 'dot' });
|
|
41
|
+
i++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else if (expression[i] === '[') {
|
|
45
|
+
i++; // skip '['
|
|
46
|
+
if (expression[i] === '?' && expression[i + 1] === '(') {
|
|
47
|
+
// Filter expression: find matching closing )]
|
|
48
|
+
i += 2; // skip '?('
|
|
49
|
+
let depth = 1;
|
|
50
|
+
let filterStart = i;
|
|
51
|
+
while (i < expression.length && depth > 0) {
|
|
52
|
+
if (expression[i] === '(') {
|
|
53
|
+
depth++;
|
|
54
|
+
}
|
|
55
|
+
else if (expression[i] === ')') {
|
|
56
|
+
depth--;
|
|
57
|
+
}
|
|
58
|
+
if (depth > 0) {
|
|
59
|
+
i++;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (depth !== 0) {
|
|
63
|
+
throw new Error(`Unmatched filter parenthesis in: ${expression}`);
|
|
64
|
+
}
|
|
65
|
+
const raw = expression.substring(filterStart, i);
|
|
66
|
+
i++; // skip ')'
|
|
67
|
+
if (expression[i] !== ']') {
|
|
68
|
+
throw new Error(`Expected ] after filter expression in: ${expression}`);
|
|
69
|
+
}
|
|
70
|
+
i++; // skip ']'
|
|
71
|
+
tokens.push({ type: 'filter', raw });
|
|
72
|
+
}
|
|
73
|
+
else if (expression[i] === '*') {
|
|
74
|
+
i++; // skip '*'
|
|
75
|
+
if (expression[i] !== ']') {
|
|
76
|
+
throw new Error(`Expected ] after * in: ${expression}`);
|
|
77
|
+
}
|
|
78
|
+
i++; // skip ']'
|
|
79
|
+
tokens.push({ type: 'wildcard' });
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Index or slice: read until ']'
|
|
83
|
+
const bracketStart = i;
|
|
84
|
+
while (i < expression.length && expression[i] !== ']') {
|
|
85
|
+
i++;
|
|
86
|
+
}
|
|
87
|
+
if (i >= expression.length) {
|
|
88
|
+
throw new Error(`Unmatched [ in: ${expression}`);
|
|
89
|
+
}
|
|
90
|
+
const content = expression.substring(bracketStart, i);
|
|
91
|
+
i++; // skip ']'
|
|
92
|
+
if (content.includes(':')) {
|
|
93
|
+
// Slice notation
|
|
94
|
+
const parts = content.split(':');
|
|
95
|
+
const start = parts[0] !== '' ? parseInt(parts[0], 10) : undefined;
|
|
96
|
+
const end = parts[1] !== '' ? parseInt(parts[1], 10) : undefined;
|
|
97
|
+
if ((parts[0] !== '' && isNaN(start)) ||
|
|
98
|
+
(parts[1] !== '' && isNaN(end))) {
|
|
99
|
+
throw new Error(`Invalid slice notation: [${content}]`);
|
|
100
|
+
}
|
|
101
|
+
tokens.push({ type: 'slice', start, end });
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// Integer index
|
|
105
|
+
const value = parseInt(content, 10);
|
|
106
|
+
if (isNaN(value)) {
|
|
107
|
+
throw new Error(`Invalid bracket expression: [${content}]`);
|
|
108
|
+
}
|
|
109
|
+
tokens.push({ type: 'index', value });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else if (expression[i] === '*') {
|
|
114
|
+
tokens.push({ type: 'wildcard' });
|
|
115
|
+
i++;
|
|
116
|
+
}
|
|
117
|
+
else if (isIdentStart(expression[i])) {
|
|
118
|
+
const start = i;
|
|
119
|
+
while (i < expression.length && isIdentChar(expression[i])) {
|
|
120
|
+
i++;
|
|
121
|
+
}
|
|
122
|
+
tokens.push({ type: 'property', name: expression.substring(start, i) });
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
throw new Error(`Unexpected character '${expression[i]}' at position ${i} in: ${expression}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return tokens;
|
|
129
|
+
}
|
|
130
|
+
function isIdentStart(ch) {
|
|
131
|
+
return /[a-zA-Z_]/.test(ch);
|
|
132
|
+
}
|
|
133
|
+
function isIdentChar(ch) {
|
|
134
|
+
return /[a-zA-Z0-9_-]/.test(ch);
|
|
135
|
+
}
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// Filter expression parser
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
const COMPARISON_OPS = [
|
|
140
|
+
'!==',
|
|
141
|
+
'===',
|
|
142
|
+
'!=',
|
|
143
|
+
'==',
|
|
144
|
+
'<=',
|
|
145
|
+
'>=',
|
|
146
|
+
'<',
|
|
147
|
+
'>',
|
|
148
|
+
];
|
|
149
|
+
function parseFilterCondition(raw) {
|
|
150
|
+
// Split on && (top-level only, respecting quotes)
|
|
151
|
+
const parts = splitOnLogicalAnd(raw);
|
|
152
|
+
if (parts.length > 1) {
|
|
153
|
+
return {
|
|
154
|
+
kind: 'and',
|
|
155
|
+
conditions: parts.map((p) => parseSingleCondition(p.trim())),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return parseSingleCondition(raw.trim());
|
|
159
|
+
}
|
|
160
|
+
function splitOnLogicalAnd(raw) {
|
|
161
|
+
const parts = [];
|
|
162
|
+
let current = '';
|
|
163
|
+
let inString = null;
|
|
164
|
+
let depth = 0;
|
|
165
|
+
for (let i = 0; i < raw.length; i++) {
|
|
166
|
+
const ch = raw[i];
|
|
167
|
+
if (inString) {
|
|
168
|
+
current += ch;
|
|
169
|
+
if (ch === inString && raw[i - 1] !== '\\') {
|
|
170
|
+
inString = null;
|
|
171
|
+
}
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (ch === '"' || ch === "'") {
|
|
175
|
+
inString = ch;
|
|
176
|
+
current += ch;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (ch === '(') {
|
|
180
|
+
depth++;
|
|
181
|
+
current += ch;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (ch === ')') {
|
|
185
|
+
depth--;
|
|
186
|
+
current += ch;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (depth === 0 && ch === '&' && raw[i + 1] === '&') {
|
|
190
|
+
parts.push(current);
|
|
191
|
+
current = '';
|
|
192
|
+
i++; // skip second &
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
current += ch;
|
|
196
|
+
}
|
|
197
|
+
parts.push(current);
|
|
198
|
+
return parts;
|
|
199
|
+
}
|
|
200
|
+
function parseSingleCondition(expr) {
|
|
201
|
+
// Try to find a comparison operator
|
|
202
|
+
for (const op of COMPARISON_OPS) {
|
|
203
|
+
const idx = findOperatorIndex(expr, op);
|
|
204
|
+
if (idx !== -1) {
|
|
205
|
+
const left = expr.substring(0, idx).trim();
|
|
206
|
+
const right = expr.substring(idx + op.length).trim();
|
|
207
|
+
return {
|
|
208
|
+
kind: 'comparison',
|
|
209
|
+
left: parseOperand(left),
|
|
210
|
+
op,
|
|
211
|
+
right: parseOperand(right),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// No operator found — existence check
|
|
216
|
+
if (expr.startsWith('@.')) {
|
|
217
|
+
return { kind: 'existence', segments: expr.substring(2).split('.') };
|
|
218
|
+
}
|
|
219
|
+
throw new Error(`Cannot parse filter condition: ${expr}`);
|
|
220
|
+
}
|
|
221
|
+
function findOperatorIndex(expr, op) {
|
|
222
|
+
let inString = null;
|
|
223
|
+
for (let i = 0; i < expr.length - op.length + 1; i++) {
|
|
224
|
+
const ch = expr[i];
|
|
225
|
+
if (inString) {
|
|
226
|
+
if (ch === inString && expr[i - 1] !== '\\') {
|
|
227
|
+
inString = null;
|
|
228
|
+
}
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (ch === '"' || ch === "'") {
|
|
232
|
+
inString = ch;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (expr.substring(i, i + op.length) === op) {
|
|
236
|
+
// For single-char ops (< > = !), make sure we're not part of a multi-char op
|
|
237
|
+
if (op.length === 1) {
|
|
238
|
+
const next = expr[i + 1];
|
|
239
|
+
if (next === '=' || (op === '!' && next === '=')) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (op.length === 2 && (op === '==' || op === '!=')) {
|
|
244
|
+
const next = expr[i + 2];
|
|
245
|
+
if (next === '=') {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return i;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return -1;
|
|
253
|
+
}
|
|
254
|
+
function parseOperand(raw) {
|
|
255
|
+
const trimmed = raw.trim();
|
|
256
|
+
// @.path
|
|
257
|
+
if (trimmed.startsWith('@.')) {
|
|
258
|
+
return { kind: 'path', segments: trimmed.substring(2).split('.') };
|
|
259
|
+
}
|
|
260
|
+
// String literal
|
|
261
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
262
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
263
|
+
return { kind: 'literal', value: trimmed.slice(1, -1) };
|
|
264
|
+
}
|
|
265
|
+
// Boolean
|
|
266
|
+
if (trimmed === 'true') {
|
|
267
|
+
return { kind: 'literal', value: true };
|
|
268
|
+
}
|
|
269
|
+
if (trimmed === 'false') {
|
|
270
|
+
return { kind: 'literal', value: false };
|
|
271
|
+
}
|
|
272
|
+
// Null
|
|
273
|
+
if (trimmed === 'null') {
|
|
274
|
+
return { kind: 'literal', value: null };
|
|
275
|
+
}
|
|
276
|
+
// Number
|
|
277
|
+
const num = Number(trimmed);
|
|
278
|
+
if (!isNaN(num) && trimmed !== '') {
|
|
279
|
+
return { kind: 'literal', value: num };
|
|
280
|
+
}
|
|
281
|
+
throw new Error(`Cannot parse filter operand: ${raw}`);
|
|
282
|
+
}
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
// Parser: tokens → path segments
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
function parse(tokens) {
|
|
287
|
+
const segments = [];
|
|
288
|
+
let i = 0;
|
|
289
|
+
while (i < tokens.length) {
|
|
290
|
+
const tok = tokens[i];
|
|
291
|
+
switch (tok.type) {
|
|
292
|
+
case 'root':
|
|
293
|
+
segments.push({ type: 'root' });
|
|
294
|
+
i++;
|
|
295
|
+
break;
|
|
296
|
+
case 'dot':
|
|
297
|
+
// Next token should be a property or wildcard
|
|
298
|
+
i++;
|
|
299
|
+
break;
|
|
300
|
+
case 'dotdot': {
|
|
301
|
+
// Recursive descent — wrap the next meaningful segment
|
|
302
|
+
i++;
|
|
303
|
+
const next = tokens[i];
|
|
304
|
+
if (!next) {
|
|
305
|
+
throw new Error('Unexpected end after ..');
|
|
306
|
+
}
|
|
307
|
+
if (next.type === 'property') {
|
|
308
|
+
segments.push({
|
|
309
|
+
type: 'recursiveDescent',
|
|
310
|
+
target: { type: 'property', name: next.name },
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
else if (next.type === 'wildcard') {
|
|
314
|
+
segments.push({
|
|
315
|
+
type: 'recursiveDescent',
|
|
316
|
+
target: { type: 'wildcard' },
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
else if (next.type === 'filter') {
|
|
320
|
+
segments.push({
|
|
321
|
+
type: 'recursiveDescent',
|
|
322
|
+
target: {
|
|
323
|
+
type: 'filter',
|
|
324
|
+
condition: parseFilterCondition(next.raw),
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
throw new Error(`Unexpected token after ..: ${next.type}`);
|
|
330
|
+
}
|
|
331
|
+
i++;
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
case 'property':
|
|
335
|
+
segments.push({ type: 'property', name: tok.name });
|
|
336
|
+
i++;
|
|
337
|
+
break;
|
|
338
|
+
case 'index':
|
|
339
|
+
segments.push({ type: 'index', value: tok.value });
|
|
340
|
+
i++;
|
|
341
|
+
break;
|
|
342
|
+
case 'slice':
|
|
343
|
+
segments.push({ type: 'slice', start: tok.start, end: tok.end });
|
|
344
|
+
i++;
|
|
345
|
+
break;
|
|
346
|
+
case 'wildcard':
|
|
347
|
+
segments.push({ type: 'wildcard' });
|
|
348
|
+
i++;
|
|
349
|
+
break;
|
|
350
|
+
case 'filter': {
|
|
351
|
+
const condition = parseFilterCondition(tok.raw);
|
|
352
|
+
// Check if next token is an index — combine into filterThenIndex
|
|
353
|
+
if (i + 1 < tokens.length && tokens[i + 1].type === 'index') {
|
|
354
|
+
const idxTok = tokens[i + 1];
|
|
355
|
+
segments.push({
|
|
356
|
+
type: 'filterThenIndex',
|
|
357
|
+
condition,
|
|
358
|
+
index: idxTok.value,
|
|
359
|
+
});
|
|
360
|
+
i += 2;
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
segments.push({ type: 'filter', condition });
|
|
364
|
+
i++;
|
|
365
|
+
}
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return segments;
|
|
371
|
+
}
|
|
372
|
+
// ---------------------------------------------------------------------------
|
|
373
|
+
// Evaluator
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
375
|
+
function evaluate(data, segments) {
|
|
376
|
+
let current = [];
|
|
377
|
+
for (const seg of segments) {
|
|
378
|
+
switch (seg.type) {
|
|
379
|
+
case 'root':
|
|
380
|
+
current = [data];
|
|
381
|
+
break;
|
|
382
|
+
case 'property':
|
|
383
|
+
current = applyProperty(current, seg.name);
|
|
384
|
+
break;
|
|
385
|
+
case 'index':
|
|
386
|
+
current = applyIndex(current, seg.value);
|
|
387
|
+
break;
|
|
388
|
+
case 'slice':
|
|
389
|
+
current = applySlice(current, seg.start, seg.end);
|
|
390
|
+
break;
|
|
391
|
+
case 'wildcard':
|
|
392
|
+
current = applyWildcard(current);
|
|
393
|
+
break;
|
|
394
|
+
case 'filter':
|
|
395
|
+
current = applyFilter(current, seg.condition);
|
|
396
|
+
break;
|
|
397
|
+
case 'filterThenIndex':
|
|
398
|
+
current = applyFilterThenIndex(current, seg.condition, seg.index);
|
|
399
|
+
break;
|
|
400
|
+
case 'recursiveDescent':
|
|
401
|
+
current = applyRecursiveDescent(current, seg.target);
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return current;
|
|
406
|
+
}
|
|
407
|
+
function applyProperty(nodes, name) {
|
|
408
|
+
const results = [];
|
|
409
|
+
for (const node of nodes) {
|
|
410
|
+
if (node !== null && typeof node === 'object' && !Array.isArray(node)) {
|
|
411
|
+
const val = node[name];
|
|
412
|
+
if (val !== undefined) {
|
|
413
|
+
results.push(val);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return results;
|
|
418
|
+
}
|
|
419
|
+
function applyIndex(nodes, index) {
|
|
420
|
+
const results = [];
|
|
421
|
+
for (const node of nodes) {
|
|
422
|
+
if (Array.isArray(node)) {
|
|
423
|
+
const actual = index < 0 ? node.length + index : index;
|
|
424
|
+
if (actual >= 0 && actual < node.length) {
|
|
425
|
+
results.push(node[actual]);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return results;
|
|
430
|
+
}
|
|
431
|
+
function applySlice(nodes, start, end) {
|
|
432
|
+
const results = [];
|
|
433
|
+
for (const node of nodes) {
|
|
434
|
+
if (Array.isArray(node)) {
|
|
435
|
+
const s = start !== undefined ? (start < 0 ? node.length + start : start) : 0;
|
|
436
|
+
const e = end !== undefined ? (end < 0 ? node.length + end : end) : node.length;
|
|
437
|
+
const sliced = node.slice(s, e);
|
|
438
|
+
for (const item of sliced) {
|
|
439
|
+
results.push(item);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return results;
|
|
444
|
+
}
|
|
445
|
+
function applyWildcard(nodes) {
|
|
446
|
+
const results = [];
|
|
447
|
+
for (const node of nodes) {
|
|
448
|
+
if (Array.isArray(node)) {
|
|
449
|
+
for (const item of node) {
|
|
450
|
+
results.push(item);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
else if (node !== null && typeof node === 'object') {
|
|
454
|
+
for (const val of Object.values(node)) {
|
|
455
|
+
results.push(val);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return results;
|
|
460
|
+
}
|
|
461
|
+
function applyFilter(nodes, condition) {
|
|
462
|
+
const results = [];
|
|
463
|
+
for (const node of nodes) {
|
|
464
|
+
if (Array.isArray(node)) {
|
|
465
|
+
for (const item of node) {
|
|
466
|
+
if (evaluateCondition(item, condition)) {
|
|
467
|
+
results.push(item);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return results;
|
|
473
|
+
}
|
|
474
|
+
function applyFilterThenIndex(nodes, condition, index) {
|
|
475
|
+
const results = [];
|
|
476
|
+
for (const node of nodes) {
|
|
477
|
+
if (Array.isArray(node)) {
|
|
478
|
+
const filtered = node.filter((item) => evaluateCondition(item, condition));
|
|
479
|
+
const actual = index < 0 ? filtered.length + index : index;
|
|
480
|
+
if (actual >= 0 && actual < filtered.length) {
|
|
481
|
+
results.push(filtered[actual]);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return results;
|
|
486
|
+
}
|
|
487
|
+
function applyRecursiveDescent(nodes, target) {
|
|
488
|
+
// Collect all descendants from every current node
|
|
489
|
+
const allDescendants = [];
|
|
490
|
+
for (const node of nodes) {
|
|
491
|
+
collectDescendants(node, allDescendants);
|
|
492
|
+
}
|
|
493
|
+
// Apply the target segment to each descendant
|
|
494
|
+
switch (target.type) {
|
|
495
|
+
case 'property': {
|
|
496
|
+
const results = [];
|
|
497
|
+
for (const d of allDescendants) {
|
|
498
|
+
if (d !== null && typeof d === 'object' && !Array.isArray(d)) {
|
|
499
|
+
const val = d[target.name];
|
|
500
|
+
if (val !== undefined) {
|
|
501
|
+
results.push(val);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return results;
|
|
506
|
+
}
|
|
507
|
+
case 'wildcard': {
|
|
508
|
+
const results = [];
|
|
509
|
+
for (const d of allDescendants) {
|
|
510
|
+
if (Array.isArray(d)) {
|
|
511
|
+
for (const item of d) {
|
|
512
|
+
results.push(item);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return results;
|
|
517
|
+
}
|
|
518
|
+
case 'filter': {
|
|
519
|
+
const results = [];
|
|
520
|
+
for (const d of allDescendants) {
|
|
521
|
+
if (Array.isArray(d)) {
|
|
522
|
+
for (const item of d) {
|
|
523
|
+
if (evaluateCondition(item, target.condition)) {
|
|
524
|
+
results.push(item);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return results;
|
|
530
|
+
}
|
|
531
|
+
default:
|
|
532
|
+
throw new Error(`Unsupported recursive descent target: ${target.type}`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function collectDescendants(node, out) {
|
|
536
|
+
out.push(node);
|
|
537
|
+
if (node !== null && typeof node === 'object') {
|
|
538
|
+
const values = Array.isArray(node)
|
|
539
|
+
? node
|
|
540
|
+
: Object.values(node);
|
|
541
|
+
for (const val of values) {
|
|
542
|
+
collectDescendants(val, out);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
// ---------------------------------------------------------------------------
|
|
547
|
+
// Filter condition evaluator
|
|
548
|
+
// ---------------------------------------------------------------------------
|
|
549
|
+
function evaluateCondition(node, condition) {
|
|
550
|
+
switch (condition.kind) {
|
|
551
|
+
case 'comparison': {
|
|
552
|
+
const left = resolveOperand(node, condition.left);
|
|
553
|
+
const right = resolveOperand(node, condition.right);
|
|
554
|
+
return compare(left, right, condition.op);
|
|
555
|
+
}
|
|
556
|
+
case 'and':
|
|
557
|
+
return condition.conditions.every((c) => evaluateCondition(node, c));
|
|
558
|
+
case 'existence': {
|
|
559
|
+
const val = resolvePathOnNode(node, condition.segments);
|
|
560
|
+
return val !== undefined;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function resolveOperand(node, operand) {
|
|
565
|
+
if (operand.kind === 'literal') {
|
|
566
|
+
return operand.value;
|
|
567
|
+
}
|
|
568
|
+
return resolvePathOnNode(node, operand.segments);
|
|
569
|
+
}
|
|
570
|
+
function resolvePathOnNode(node, segments) {
|
|
571
|
+
let current = node;
|
|
572
|
+
for (const seg of segments) {
|
|
573
|
+
if (current === null ||
|
|
574
|
+
current === undefined ||
|
|
575
|
+
typeof current !== 'object') {
|
|
576
|
+
return undefined;
|
|
577
|
+
}
|
|
578
|
+
current = current[seg];
|
|
579
|
+
}
|
|
580
|
+
return current;
|
|
581
|
+
}
|
|
582
|
+
function compare(left, right, op) {
|
|
583
|
+
switch (op) {
|
|
584
|
+
case '===':
|
|
585
|
+
return left === right;
|
|
586
|
+
case '!==':
|
|
587
|
+
return left !== right;
|
|
588
|
+
/* eslint-disable eqeqeq -- JSONPath == and != operators require loose equality */
|
|
589
|
+
case '==':
|
|
590
|
+
return left == right;
|
|
591
|
+
case '!=':
|
|
592
|
+
return left != right;
|
|
593
|
+
/* eslint-enable eqeqeq */
|
|
594
|
+
case '<':
|
|
595
|
+
return left < right;
|
|
596
|
+
case '<=':
|
|
597
|
+
return left <= right;
|
|
598
|
+
case '>':
|
|
599
|
+
return left > right;
|
|
600
|
+
case '>=':
|
|
601
|
+
return left >= right;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
// ---------------------------------------------------------------------------
|
|
605
|
+
// Public API
|
|
606
|
+
// ---------------------------------------------------------------------------
|
|
607
|
+
/**
|
|
608
|
+
* Evaluate a JSONPath expression against a data object.
|
|
609
|
+
*
|
|
610
|
+
* Drop-in replacement for `jsonpath.query()` covering the subset of JSONPath
|
|
611
|
+
* used by this project. Uses no `eval()` or dynamic code execution.
|
|
612
|
+
*
|
|
613
|
+
* @param data - The data to query
|
|
614
|
+
* @param expression - A JSONPath expression (must start with $)
|
|
615
|
+
* @returns An array of matching values
|
|
616
|
+
*/
|
|
617
|
+
function queryJsonPath(data, expression) {
|
|
618
|
+
const tokens = tokenize(expression);
|
|
619
|
+
const segments = parse(tokens);
|
|
620
|
+
return evaluate(data, segments);
|
|
621
|
+
}
|
|
622
|
+
//# sourceMappingURL=JsonPath.js.map
|