json-p3 0.1.0
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/LICENCE +21 -0
- package/README.md +252 -0
- package/dist/deep_equals.d.ts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/json-p3.cjs.js +2838 -0
- package/dist/json-p3.esm.js +2811 -0
- package/dist/json-p3.iife.js +2843 -0
- package/dist/json-p3.iife.min.js +2 -0
- package/dist/json-p3.iife.min.js.map +1 -0
- package/dist/patch/errors.d.ts +11 -0
- package/dist/patch/index.d.ts +11 -0
- package/dist/patch/patch.d.ts +157 -0
- package/dist/path/environment.d.ts +60 -0
- package/dist/path/errors.d.ts +50 -0
- package/dist/path/expression.d.ts +98 -0
- package/dist/path/functions/count.d.ts +7 -0
- package/dist/path/functions/function.d.ts +26 -0
- package/dist/path/functions/index.d.ts +7 -0
- package/dist/path/functions/length.d.ts +7 -0
- package/dist/path/functions/match.d.ts +24 -0
- package/dist/path/functions/search.d.ts +24 -0
- package/dist/path/functions/value.d.ts +7 -0
- package/dist/path/index.d.ts +46 -0
- package/dist/path/lex.d.ts +65 -0
- package/dist/path/lru_cache.d.ts +10 -0
- package/dist/path/node.d.ts +74 -0
- package/dist/path/parse.d.ts +29 -0
- package/dist/path/path.d.ts +28 -0
- package/dist/path/selectors.d.ts +90 -0
- package/dist/path/token.d.ts +63 -0
- package/dist/path/types.d.ts +21 -0
- package/dist/pointer/errors.d.ts +27 -0
- package/dist/pointer/index.d.ts +24 -0
- package/dist/pointer/pointer.d.ts +75 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types.d.ts +25 -0
- package/package.json +83 -0
|
@@ -0,0 +1,2843 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* json-p3 version 0.1.0
|
|
3
|
+
* https://github.com/jg-rp/json-p3
|
|
4
|
+
*
|
|
5
|
+
* MIT License
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2023 James Prior
|
|
8
|
+
*
|
|
9
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
* in the Software without restriction, including without limitation the rights
|
|
12
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
* furnished to do so, subject to the following conditions:
|
|
15
|
+
*
|
|
16
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
* copies or substantial portions of the Software.
|
|
18
|
+
*
|
|
19
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
* SOFTWARE.
|
|
26
|
+
*
|
|
27
|
+
*/
|
|
28
|
+
var json_p3 = (function (exports) {
|
|
29
|
+
'use strict';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Base class for all JSONPath errors.
|
|
33
|
+
*/
|
|
34
|
+
class JSONPathError extends Error {
|
|
35
|
+
constructor(message, token) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.message = message;
|
|
38
|
+
this.token = token;
|
|
39
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
40
|
+
this.name = "JSONPathError";
|
|
41
|
+
this.message = withErrorContext(message, token);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function withErrorContext(message, token) {
|
|
45
|
+
if (token.input.length <= 9) {
|
|
46
|
+
return `${message} ('${token.input}':${token.index})`;
|
|
47
|
+
}
|
|
48
|
+
if (token.index > token.input.length - 5) {
|
|
49
|
+
return `${message} ('${token.input.slice(token.input.length - 9)}':${token.index})`;
|
|
50
|
+
}
|
|
51
|
+
if (token.index - 4 < 0) {
|
|
52
|
+
return `${message} ('${token.input.slice(0, 9)}':${token.index})`;
|
|
53
|
+
}
|
|
54
|
+
return `${message} ('${token.input.slice(token.index - 4, token.index + 5)}':${token.index})`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Error thrown due to unexpected, internal path tokenization problems.
|
|
59
|
+
*/
|
|
60
|
+
class JSONPathLexerError extends JSONPathError {
|
|
61
|
+
constructor(message, token) {
|
|
62
|
+
super(message, token);
|
|
63
|
+
this.message = message;
|
|
64
|
+
this.token = token;
|
|
65
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
66
|
+
this.name = "JSONPathLexerError";
|
|
67
|
+
this.message = withErrorContext(message, token);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Error thrown due to type errors when evaluating filter expressions.
|
|
73
|
+
*/
|
|
74
|
+
class JSONPathTypeError extends JSONPathError {
|
|
75
|
+
constructor(message, token) {
|
|
76
|
+
super(message, token);
|
|
77
|
+
this.message = message;
|
|
78
|
+
this.token = token;
|
|
79
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
80
|
+
this.name = "JSONPathTypeError";
|
|
81
|
+
this.message = withErrorContext(message, token);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Error thrown due to out of range indices.
|
|
87
|
+
*/
|
|
88
|
+
class JSONPathIndexError extends JSONPathError {
|
|
89
|
+
constructor(message, token) {
|
|
90
|
+
super(message, token);
|
|
91
|
+
this.message = message;
|
|
92
|
+
this.token = token;
|
|
93
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
94
|
+
this.name = "JSONPathIndexError";
|
|
95
|
+
this.message = withErrorContext(message, token);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Error thrown when attempting to retrieve a filter function that has not
|
|
101
|
+
* been registered.
|
|
102
|
+
*/
|
|
103
|
+
class UndefinedFilterFunctionError extends JSONPathError {
|
|
104
|
+
constructor(message, token) {
|
|
105
|
+
super(message, token);
|
|
106
|
+
this.message = message;
|
|
107
|
+
this.token = token;
|
|
108
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
109
|
+
this.name = "UndefinedFilterFunctionError";
|
|
110
|
+
this.message = withErrorContext(message, token);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Error thrown due to syntax errors found during parsing a JSONPath query.
|
|
116
|
+
*/
|
|
117
|
+
class JSONPathSyntaxError extends JSONPathError {
|
|
118
|
+
constructor(message, token) {
|
|
119
|
+
super(message, token);
|
|
120
|
+
this.message = message;
|
|
121
|
+
this.token = token;
|
|
122
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
123
|
+
this.name = "JSONPathSyntaxError";
|
|
124
|
+
this.message = withErrorContext(message, token);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Common types and type predicates.
|
|
130
|
+
*/
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* A JSON-like value.
|
|
134
|
+
*/
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* A type predicate for the Array object.
|
|
138
|
+
*/
|
|
139
|
+
function isArray(value) {
|
|
140
|
+
return Array.isArray(value);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* A type predicate for object.
|
|
145
|
+
*/
|
|
146
|
+
function isObject(value) {
|
|
147
|
+
const _type = typeof value;
|
|
148
|
+
return value !== null && _type === "object" || _type === "function" ? true : false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* A type predicate for a string primitive.
|
|
153
|
+
*/
|
|
154
|
+
function isString(value) {
|
|
155
|
+
return typeof value === "string";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* A type predicate for a number primitive.
|
|
160
|
+
*/
|
|
161
|
+
function isNumber(value) {
|
|
162
|
+
return typeof value === "number";
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Deep equality of JSON-like values.
|
|
167
|
+
*
|
|
168
|
+
* No attempt is made to handle function objects, recursive data
|
|
169
|
+
* structures, NaNs, sparse arrays, primitive wrapper objects....
|
|
170
|
+
*
|
|
171
|
+
* We're not using JSON.stringify because we want objects with the same
|
|
172
|
+
* entries in a different order to compare equal.
|
|
173
|
+
*/
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
177
|
+
function deepEquals(a, b) {
|
|
178
|
+
if (a === b) {
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
if (Array.isArray(a)) {
|
|
182
|
+
if (Array.isArray(b)) {
|
|
183
|
+
if (a.length !== b.length) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
for (let i = 0; i < a.length; i++) {
|
|
187
|
+
if (!deepEquals(a[i], b[i])) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
} else if (isObject(a) && isObject(b)) {
|
|
195
|
+
const keysA = Object.keys(a);
|
|
196
|
+
const keysB = Object.keys(b);
|
|
197
|
+
if (keysA.length !== keysB.length) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
for (const key of keysA) {
|
|
201
|
+
if (!deepEquals(a[key], b[key])) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* The type of a JSONPath filter function parameter or return value, as
|
|
212
|
+
* described in See section 2.4.1 of draft-ietf-jsonpath-base-20.
|
|
213
|
+
*/
|
|
214
|
+
let FunctionExpressionType = /*#__PURE__*/function (FunctionExpressionType) {
|
|
215
|
+
FunctionExpressionType["ValueType"] = "ValueType";
|
|
216
|
+
FunctionExpressionType["LogicalType"] = "LogicalType";
|
|
217
|
+
FunctionExpressionType["NodesType"] = "NodesType";
|
|
218
|
+
return FunctionExpressionType;
|
|
219
|
+
}({});
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* A JSONPath filter function definition.
|
|
223
|
+
*/
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Base class for all JSON Pointer errors.
|
|
227
|
+
*/
|
|
228
|
+
class JSONPointerError extends Error {
|
|
229
|
+
constructor(message) {
|
|
230
|
+
super(message);
|
|
231
|
+
this.message = message;
|
|
232
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
233
|
+
this.name = "JSONPointerError";
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
class JSONPointerResolutionError extends JSONPointerError {
|
|
237
|
+
constructor(message) {
|
|
238
|
+
super(message);
|
|
239
|
+
this.message = message;
|
|
240
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
241
|
+
this.name = "JSONPointerResolutionError";
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
class JSONPointerIndexError extends JSONPointerResolutionError {
|
|
245
|
+
constructor(message) {
|
|
246
|
+
super(message);
|
|
247
|
+
this.message = message;
|
|
248
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
249
|
+
this.name = "JSONPointerIndexError";
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
class JSONPointerKeyError extends JSONPointerResolutionError {
|
|
253
|
+
constructor(message) {
|
|
254
|
+
super(message);
|
|
255
|
+
this.message = message;
|
|
256
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
257
|
+
this.name = "JSONPointerKeyError";
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
class JSONPointerSyntaxError extends JSONPointerError {
|
|
261
|
+
constructor(message) {
|
|
262
|
+
super(message);
|
|
263
|
+
this.message = message;
|
|
264
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
265
|
+
this.name = "JSONPointerSyntaxError";
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
class JSONPointerTypeError extends JSONPointerResolutionError {
|
|
269
|
+
constructor(message) {
|
|
270
|
+
super(message);
|
|
271
|
+
this.message = message;
|
|
272
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
273
|
+
this.name = "JSONPointerTypeError";
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The symbol indicating the absence of a JSON value.
|
|
279
|
+
*/
|
|
280
|
+
const UNDEFINED = Symbol.for("jsonpointer.undefined");
|
|
281
|
+
/**
|
|
282
|
+
* Identify a single value in JSON-like data, as per RFC 6901.
|
|
283
|
+
*/
|
|
284
|
+
class JSONPointer {
|
|
285
|
+
#pointer;
|
|
286
|
+
/**
|
|
287
|
+
* @param pointer - A string representation of a JSON Pointer.
|
|
288
|
+
*/
|
|
289
|
+
constructor(pointer) {
|
|
290
|
+
this.tokens = this.parse(pointer);
|
|
291
|
+
this.#pointer = JSONPointer.encode(this.tokens);
|
|
292
|
+
}
|
|
293
|
+
static encode(tokens) {
|
|
294
|
+
if (!tokens.length) return "";
|
|
295
|
+
return (
|
|
296
|
+
// eslint-disable-next-line prefer-template
|
|
297
|
+
"/" + tokens.map(token => token.replaceAll("~", "~0").replaceAll("/", "~1")).join("/")
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Resolve this pointer against JSON-like data _value_.
|
|
303
|
+
*
|
|
304
|
+
* @param value - The target JSON-like value, possibly loaded using
|
|
305
|
+
* `JSON.parse()`.
|
|
306
|
+
* @param fallback - A default value to return if _value_ has no
|
|
307
|
+
* path matching `pointer`.
|
|
308
|
+
* @returns The value identified by _pointer_ or, if given, the fallback
|
|
309
|
+
* value in the even of a `JSONPointerResolutionError`.
|
|
310
|
+
*
|
|
311
|
+
* @throws {@link JSONPointerResolutionError}
|
|
312
|
+
* If the value pointed to by _pointer_ does not exist in _value_, and
|
|
313
|
+
* no fallback value is given.
|
|
314
|
+
*/
|
|
315
|
+
resolve(value) {
|
|
316
|
+
let fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : UNDEFINED;
|
|
317
|
+
try {
|
|
318
|
+
return this.tokens.reduce(this.getItem.bind(this), value);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
if (error instanceof JSONPointerResolutionError && fallback !== UNDEFINED) {
|
|
321
|
+
return fallback;
|
|
322
|
+
}
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// TODO: add `resolveWithFallback` to handle explicit `undefined`
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
*
|
|
331
|
+
* @param value -
|
|
332
|
+
* @returns
|
|
333
|
+
*/
|
|
334
|
+
resolveWithParent(value) {
|
|
335
|
+
if (!this.tokens.length) return [UNDEFINED, this.resolve(value)];
|
|
336
|
+
const parent = this.tokens.slice(0, this.tokens.length - 1).reduce(this.getItem.bind(this), value);
|
|
337
|
+
try {
|
|
338
|
+
return [parent, this.getItem(parent, this.tokens[this.tokens.length - 1], this.tokens.length - 1)];
|
|
339
|
+
} catch (error) {
|
|
340
|
+
if (error instanceof JSONPointerIndexError || error instanceof JSONPointerKeyError) {
|
|
341
|
+
return [parent, UNDEFINED];
|
|
342
|
+
}
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
*
|
|
349
|
+
* @returns
|
|
350
|
+
*/
|
|
351
|
+
toString() {
|
|
352
|
+
return this.#pointer;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Return _true_ if this pointer points to a child of _pointer_.
|
|
357
|
+
*/
|
|
358
|
+
isRelativeTo(pointer) {
|
|
359
|
+
return pointer.tokens.length < this.tokens.length && this.tokens.slice(0, pointer.tokens.length).every((t, i) => t === pointer.tokens[i]);
|
|
360
|
+
}
|
|
361
|
+
parse(pointer) {
|
|
362
|
+
if (pointer.length && !pointer.startsWith("/")) {
|
|
363
|
+
throw new JSONPointerSyntaxError(`"${pointer}" pointers must start with a slash or be the empty string`);
|
|
364
|
+
}
|
|
365
|
+
return pointer.split("/").map(token => token.replaceAll("~1", "/").replaceAll("~0", "~")).slice(1);
|
|
366
|
+
}
|
|
367
|
+
getItem(val, token, idx) {
|
|
368
|
+
// NOTE:
|
|
369
|
+
// - string primitives "have own" indices and `length`.
|
|
370
|
+
// - Arrays have a `length` property.
|
|
371
|
+
// - A property might exist with the value `undefined` or `null`.
|
|
372
|
+
// - obj[1] is equivalent to obj["1"].
|
|
373
|
+
if (isArray(val)) {
|
|
374
|
+
if (token !== "length" && Object.hasOwn(val, token)) {
|
|
375
|
+
return val[Number(token)];
|
|
376
|
+
} else {
|
|
377
|
+
throw new JSONPointerIndexError(`index out of range '${JSONPointer.encode(this.tokens.slice(0, idx + 1))}'`);
|
|
378
|
+
}
|
|
379
|
+
} else if (isObject(val)) {
|
|
380
|
+
if (Object.hasOwn(val, token)) {
|
|
381
|
+
return val[token];
|
|
382
|
+
} else {
|
|
383
|
+
throw new JSONPointerKeyError(`no such property '${JSONPointer.encode(this.tokens.slice(0, idx + 1))}'`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
throw new JSONPointerTypeError(`found primitive value, expected an object '${JSONPointer.encode(this.tokens.slice(0, idx + 1))}'`);
|
|
387
|
+
}
|
|
388
|
+
_join(pointer) {
|
|
389
|
+
if (!isString(pointer)) {
|
|
390
|
+
throw new JSONPointerTypeError(`join() requires string arguments, found ${typeof pointer}`);
|
|
391
|
+
}
|
|
392
|
+
if (pointer.startsWith("/")) {
|
|
393
|
+
return new JSONPointer(pointer);
|
|
394
|
+
}
|
|
395
|
+
const tokens = this.tokens.concat(pointer.split("/").map(token => token.replaceAll("~1", "/").replaceAll("~0", "~")));
|
|
396
|
+
return new JSONPointer(JSONPointer.encode(tokens));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Join this pointer with _tokens_.
|
|
401
|
+
* @param tokens - JSON Pointer strings, possibly without leading slashes.
|
|
402
|
+
* If a token or "part" does have a leading slash, the previous pointer is
|
|
403
|
+
* ignored and a new `JSONPointer` is created, then processing of the
|
|
404
|
+
* remaining tokens continues.
|
|
405
|
+
* @returns A new JSON Pointer that is the concatenation of all tokens or
|
|
406
|
+
* "parts".
|
|
407
|
+
*/
|
|
408
|
+
join() {
|
|
409
|
+
for (var _len = arguments.length, tokens = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
410
|
+
tokens[_key] = arguments[_key];
|
|
411
|
+
}
|
|
412
|
+
if (!tokens.length) {
|
|
413
|
+
return this;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
417
|
+
let pointer = this;
|
|
418
|
+
for (const tok of tokens) {
|
|
419
|
+
pointer = pointer._join(tok);
|
|
420
|
+
}
|
|
421
|
+
return pointer;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Return _true_ if this pointer can be resolved against _value_.
|
|
426
|
+
*
|
|
427
|
+
* Note that `JSONPointer.resolve()` can return legitimate falsy values
|
|
428
|
+
* that form part of the target JSON document. This method will return
|
|
429
|
+
* `true` if a falsy value is found.
|
|
430
|
+
*/
|
|
431
|
+
exists(value) {
|
|
432
|
+
try {
|
|
433
|
+
this.resolve(value);
|
|
434
|
+
} catch (error) {
|
|
435
|
+
if (error instanceof JSONPointerResolutionError) {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
throw error;
|
|
439
|
+
}
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Return this pointer's parent as a new `JSONPointer`.
|
|
445
|
+
*
|
|
446
|
+
* If this pointer points to the document root, _this_ is returned.
|
|
447
|
+
*/
|
|
448
|
+
parent() {
|
|
449
|
+
if (!this.tokens.length) {
|
|
450
|
+
return this;
|
|
451
|
+
}
|
|
452
|
+
return new JSONPointer(JSONPointer.encode(this.tokens.slice(0, this.tokens.length - 1)));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// TODO: to (relative pointer)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Resolve JSON Pointer _pointer_ against JSON-like data _value_.
|
|
460
|
+
*
|
|
461
|
+
* @param pointer - A string representation of a JSON pointer.
|
|
462
|
+
* @param value - The target JSON-like value, possibly loaded using
|
|
463
|
+
* `JSON.parse()`.
|
|
464
|
+
* @param fallback - A default value to return if _value_ has no
|
|
465
|
+
* path matching `pointer`.
|
|
466
|
+
* @returns The value identified by _pointer_ or, if given, the fallback
|
|
467
|
+
* value in the even of a `JSONPointerResolutionError`.
|
|
468
|
+
*
|
|
469
|
+
* @throws {@link JSONPointerResolutionError}
|
|
470
|
+
* If the value pointed to by _pointer_ does not exist in _value_, and
|
|
471
|
+
* no fallback value is given.
|
|
472
|
+
*
|
|
473
|
+
* @throws {@link JSONPointerSyntaxError}
|
|
474
|
+
* If _pointer_ is malformed according to RFC 6901.
|
|
475
|
+
*/
|
|
476
|
+
function resolve(pointer, value) {
|
|
477
|
+
let fallback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : UNDEFINED;
|
|
478
|
+
return new JSONPointer(pointer).resolve(value, fallback);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
var index$3 = /*#__PURE__*/Object.freeze({
|
|
482
|
+
__proto__: null,
|
|
483
|
+
JSONPointer: JSONPointer,
|
|
484
|
+
JSONPointerError: JSONPointerError,
|
|
485
|
+
JSONPointerIndexError: JSONPointerIndexError,
|
|
486
|
+
JSONPointerKeyError: JSONPointerKeyError,
|
|
487
|
+
JSONPointerResolutionError: JSONPointerResolutionError,
|
|
488
|
+
JSONPointerSyntaxError: JSONPointerSyntaxError,
|
|
489
|
+
JSONPointerTypeError: JSONPointerTypeError,
|
|
490
|
+
UNDEFINED: UNDEFINED,
|
|
491
|
+
resolve: resolve
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* The pair of a JSON value and its location found in the target JSON value.
|
|
496
|
+
*/
|
|
497
|
+
class JSONPathNode {
|
|
498
|
+
/**
|
|
499
|
+
* The normalized path to this node in the target JSON value.
|
|
500
|
+
*/
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* @param value - The JSON value found at _location_.
|
|
504
|
+
* @param location - The parts of a normalized path to _value_.
|
|
505
|
+
* @param root - The target value at the top of the JSON node tree.
|
|
506
|
+
*/
|
|
507
|
+
constructor(value, location, root) {
|
|
508
|
+
this.value = value;
|
|
509
|
+
this.location = location;
|
|
510
|
+
this.root = root;
|
|
511
|
+
this.path =
|
|
512
|
+
// eslint-disable-next-line prefer-template
|
|
513
|
+
"$" + location.map(s => isString(s) ? `['${s}']` : `[${s}]`).join("");
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Return this node's location as a {@link JSONPointer}.
|
|
518
|
+
*/
|
|
519
|
+
toPointer() {
|
|
520
|
+
if (!this.location.length) {
|
|
521
|
+
return new JSONPointer("");
|
|
522
|
+
}
|
|
523
|
+
return new JSONPointer(JSONPointer.encode(this.location.map(String)));
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
*
|
|
529
|
+
*/
|
|
530
|
+
class JSONPathNodeList {
|
|
531
|
+
constructor(nodes) {
|
|
532
|
+
this.nodes = nodes;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* @returns an iterator over nodes in the list.
|
|
537
|
+
*/
|
|
538
|
+
[Symbol.iterator]() {
|
|
539
|
+
return this.nodes[Symbol.iterator]();
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* @returns `true` if the node list is empty.
|
|
544
|
+
*/
|
|
545
|
+
empty() {
|
|
546
|
+
return this.nodes.length === 0;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* @returns An array containing the values at each node in the list.
|
|
551
|
+
*
|
|
552
|
+
* @see {@link valuesOrSingular} to unpack the array if there is only
|
|
553
|
+
* one node in the list.
|
|
554
|
+
*/
|
|
555
|
+
values() {
|
|
556
|
+
return this.nodes.map(node => node.value);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Like {@link values}, but returns the node's value is there is only one
|
|
561
|
+
* node in the list.
|
|
562
|
+
*/
|
|
563
|
+
valuesOrSingular() {
|
|
564
|
+
if (this.nodes.length === 1) return this.nodes[0].value;
|
|
565
|
+
return this.nodes.map(node => node.value);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* @returns An array of locations for each node in the node list.
|
|
570
|
+
*
|
|
571
|
+
* A location is an array of property names and array indices that were
|
|
572
|
+
* required to reach the node's value in the target JSON value.
|
|
573
|
+
*/
|
|
574
|
+
locations() {
|
|
575
|
+
return this.nodes.map(node => node.location);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* @returns An array of normalized path strings for each node in the list.
|
|
580
|
+
*
|
|
581
|
+
* A normalized path contains only property name and index selectors, and
|
|
582
|
+
* always uses bracketed segments, never shorthand selectors.
|
|
583
|
+
*/
|
|
584
|
+
paths() {
|
|
585
|
+
return this.nodes.map(node => node.path);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* @returns An array of {@link JSONPointer} instances, one for each node
|
|
590
|
+
* in the list.
|
|
591
|
+
*/
|
|
592
|
+
pointers() {
|
|
593
|
+
return this.nodes.map(node => node.toPointer());
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* @returns The number of nodes in the node list.
|
|
598
|
+
*/
|
|
599
|
+
get length() {
|
|
600
|
+
return this.nodes.length;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const Nothing = Symbol.for("jsonpath.nothing");
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* ValueType for JSONPath function expression tye system.
|
|
608
|
+
*/
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Object passed to FilterExpression.evaluate().
|
|
612
|
+
*/
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* A type predicate for an object with a string property.
|
|
616
|
+
*/
|
|
617
|
+
function hasStringKey(value, key) {
|
|
618
|
+
return isObject(value) && Object.hasOwn(value, key);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Base class for all filter expressions.
|
|
623
|
+
*/
|
|
624
|
+
class FilterExpression {
|
|
625
|
+
constructor(token) {
|
|
626
|
+
this.token = token;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Evaluate the filter expression in the given context.
|
|
631
|
+
* @param context - Evaluation context.
|
|
632
|
+
*/
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Return a string representation of the expression.
|
|
636
|
+
*/
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Base class for JSONPath ValueType literals.
|
|
641
|
+
*/
|
|
642
|
+
class FilterExpressionLiteral extends FilterExpression {}
|
|
643
|
+
class NullLiteral extends FilterExpressionLiteral {
|
|
644
|
+
evaluate() {
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
toString() {
|
|
648
|
+
return "null";
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
class BooleanLiteral extends FilterExpressionLiteral {
|
|
652
|
+
constructor(token, value) {
|
|
653
|
+
super(token);
|
|
654
|
+
this.token = token;
|
|
655
|
+
this.value = value;
|
|
656
|
+
}
|
|
657
|
+
evaluate() {
|
|
658
|
+
return this.value;
|
|
659
|
+
}
|
|
660
|
+
toString() {
|
|
661
|
+
return String(this.value);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
class StringLiteral extends FilterExpressionLiteral {
|
|
665
|
+
constructor(token, value) {
|
|
666
|
+
super(token);
|
|
667
|
+
this.token = token;
|
|
668
|
+
this.value = value;
|
|
669
|
+
}
|
|
670
|
+
evaluate() {
|
|
671
|
+
return this.value;
|
|
672
|
+
}
|
|
673
|
+
toString() {
|
|
674
|
+
return JSON.stringify(this.value);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
class NumberLiteral extends FilterExpressionLiteral {
|
|
678
|
+
constructor(token, value) {
|
|
679
|
+
super(token);
|
|
680
|
+
this.token = token;
|
|
681
|
+
this.value = value;
|
|
682
|
+
}
|
|
683
|
+
evaluate() {
|
|
684
|
+
return this.value;
|
|
685
|
+
}
|
|
686
|
+
toString() {
|
|
687
|
+
return String(this.value);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
class PrefixExpression extends FilterExpression {
|
|
691
|
+
constructor(token, operator, right) {
|
|
692
|
+
super(token);
|
|
693
|
+
this.token = token;
|
|
694
|
+
this.operator = operator;
|
|
695
|
+
this.right = right;
|
|
696
|
+
}
|
|
697
|
+
evaluate(context) {
|
|
698
|
+
if (this.operator === "!") {
|
|
699
|
+
const value = this.right.evaluate(context);
|
|
700
|
+
if (value instanceof JSONPathNodeList) return value.nodes.length === 0; // negated existence
|
|
701
|
+
return !isTruthy(value);
|
|
702
|
+
}
|
|
703
|
+
throw new JSONPathTypeError(`unknown operator '${this.operator}'`, this.token);
|
|
704
|
+
}
|
|
705
|
+
toString() {
|
|
706
|
+
return `${this.operator}${this.right.toString()}`;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
class InfixExpression extends FilterExpression {
|
|
710
|
+
constructor(token, left, operator, right) {
|
|
711
|
+
super(token);
|
|
712
|
+
this.token = token;
|
|
713
|
+
this.left = left;
|
|
714
|
+
this.operator = operator;
|
|
715
|
+
this.right = right;
|
|
716
|
+
}
|
|
717
|
+
evaluate(context) {
|
|
718
|
+
let left = this.left.evaluate(context);
|
|
719
|
+
if (left instanceof JSONPathNodeList && left.nodes.length === 1) left = left.nodes[0].value;
|
|
720
|
+
let right = this.right.evaluate(context);
|
|
721
|
+
if (right instanceof JSONPathNodeList && right.nodes.length === 1) right = right.nodes[0].value;
|
|
722
|
+
if (this.operator === "&&") {
|
|
723
|
+
return isTruthy(left) && isTruthy(right);
|
|
724
|
+
}
|
|
725
|
+
if (this.operator === "||") {
|
|
726
|
+
return isTruthy(left) || isTruthy(right);
|
|
727
|
+
}
|
|
728
|
+
return compare(left, this.operator, right);
|
|
729
|
+
}
|
|
730
|
+
toString() {
|
|
731
|
+
if (this.operator === "&&" || this.operator === "||") {
|
|
732
|
+
return `(${this.left.toString()} ${this.operator} ${this.right.toString()})`;
|
|
733
|
+
}
|
|
734
|
+
return `${this.left.toString()} ${this.operator} ${this.right.toString()}`;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
class LogicalExpression extends FilterExpression {
|
|
738
|
+
constructor(token, expression) {
|
|
739
|
+
super(token);
|
|
740
|
+
this.token = token;
|
|
741
|
+
this.expression = expression;
|
|
742
|
+
}
|
|
743
|
+
evaluate(context) {
|
|
744
|
+
const value = this.expression.evaluate(context);
|
|
745
|
+
if (value instanceof JSONPathNodeList) return value.nodes.length > 0; // existence
|
|
746
|
+
return isTruthy(value);
|
|
747
|
+
}
|
|
748
|
+
toString() {
|
|
749
|
+
return this.expression.toString();
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Base class for relative and absolute JSONPath query expressions.
|
|
755
|
+
*/
|
|
756
|
+
class JSONPathQuery extends FilterExpression {
|
|
757
|
+
constructor(token, path) {
|
|
758
|
+
super(token);
|
|
759
|
+
this.token = token;
|
|
760
|
+
this.path = path;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
class RelativeQuery extends JSONPathQuery {
|
|
764
|
+
evaluate(context) {
|
|
765
|
+
return this.path.query(context.currentValue);
|
|
766
|
+
}
|
|
767
|
+
toString() {
|
|
768
|
+
return `@${this.path.toString().slice(1)}`;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
class RootQuery extends JSONPathQuery {
|
|
772
|
+
evaluate(context) {
|
|
773
|
+
return this.path.query(context.rootValue);
|
|
774
|
+
}
|
|
775
|
+
toString() {
|
|
776
|
+
return this.path.toString();
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
class FunctionExtension extends FilterExpression {
|
|
780
|
+
constructor(token, name, args) {
|
|
781
|
+
super(token);
|
|
782
|
+
this.token = token;
|
|
783
|
+
this.name = name;
|
|
784
|
+
this.args = args;
|
|
785
|
+
}
|
|
786
|
+
evaluate(context) {
|
|
787
|
+
const func = context.environment.filterRegister.get(this.name);
|
|
788
|
+
if (!func) {
|
|
789
|
+
throw new UndefinedFilterFunctionError(`filter function '${this.name}' is undefined`, this.token);
|
|
790
|
+
}
|
|
791
|
+
const args = this.args.map(arg => arg.evaluate(context)).map((arg, idx) => func.argTypes[idx] !== FunctionExpressionType.NodesType && arg instanceof JSONPathNodeList ? arg.valuesOrSingular() : arg);
|
|
792
|
+
return func.call(...args);
|
|
793
|
+
}
|
|
794
|
+
toString() {
|
|
795
|
+
return `${this.name}(${this.args.map(e => e.toString()).join(", ")})`;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
*
|
|
801
|
+
* @param value -
|
|
802
|
+
*/
|
|
803
|
+
function isTruthy(value) {
|
|
804
|
+
if (value instanceof JSONPathNodeList && value.empty()) return false;
|
|
805
|
+
return !(typeof value === "boolean" && value === false);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
*
|
|
810
|
+
* @param left -
|
|
811
|
+
* @param operator -
|
|
812
|
+
* @param right -
|
|
813
|
+
*/
|
|
814
|
+
function compare(left, operator, right) {
|
|
815
|
+
switch (operator) {
|
|
816
|
+
case "==":
|
|
817
|
+
return eq(left, right);
|
|
818
|
+
case "!=":
|
|
819
|
+
return !eq(left, right);
|
|
820
|
+
case "<":
|
|
821
|
+
return lt(left, right);
|
|
822
|
+
case ">":
|
|
823
|
+
return lt(right, left);
|
|
824
|
+
case ">=":
|
|
825
|
+
return lt(right, left) || eq(left, right);
|
|
826
|
+
case "<=":
|
|
827
|
+
return lt(left, right) || eq(left, right);
|
|
828
|
+
default:
|
|
829
|
+
return false;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
834
|
+
function eq(left, right) {
|
|
835
|
+
if (right instanceof JSONPathNodeList) [left, right] = [right, left];
|
|
836
|
+
if (left instanceof JSONPathNodeList) {
|
|
837
|
+
if (right instanceof JSONPathNodeList) {
|
|
838
|
+
if (left.empty() && right.empty()) return true;
|
|
839
|
+
if (left.nodes.length === 1 && right.nodes.length === 1) return deepEquals(left.nodes[0].value, right.nodes[0].value);
|
|
840
|
+
}
|
|
841
|
+
if (left.empty()) return right === Nothing;
|
|
842
|
+
if (left.nodes.length === 1) return deepEquals(left.nodes[0].value, right);
|
|
843
|
+
return false;
|
|
844
|
+
}
|
|
845
|
+
if (left === Nothing && right === Nothing) return true;
|
|
846
|
+
return deepEquals(left, right);
|
|
847
|
+
}
|
|
848
|
+
function lt(left, right) {
|
|
849
|
+
if (isString(left) && isString(right) || isNumber(left) && isNumber(right)) return left < right;
|
|
850
|
+
return false;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
var expression = /*#__PURE__*/Object.freeze({
|
|
854
|
+
__proto__: null,
|
|
855
|
+
BooleanLiteral: BooleanLiteral,
|
|
856
|
+
FilterExpression: FilterExpression,
|
|
857
|
+
FilterExpressionLiteral: FilterExpressionLiteral,
|
|
858
|
+
FunctionExtension: FunctionExtension,
|
|
859
|
+
InfixExpression: InfixExpression,
|
|
860
|
+
JSONPathQuery: JSONPathQuery,
|
|
861
|
+
LogicalExpression: LogicalExpression,
|
|
862
|
+
NullLiteral: NullLiteral,
|
|
863
|
+
NumberLiteral: NumberLiteral,
|
|
864
|
+
PrefixExpression: PrefixExpression,
|
|
865
|
+
RelativeQuery: RelativeQuery,
|
|
866
|
+
RootQuery: RootQuery,
|
|
867
|
+
StringLiteral: StringLiteral
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
class Count {
|
|
871
|
+
argTypes = [FunctionExpressionType.NodesType];
|
|
872
|
+
returnType = FunctionExpressionType.ValueType;
|
|
873
|
+
call(nodes) {
|
|
874
|
+
return nodes.length;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
class Length {
|
|
879
|
+
argTypes = [FunctionExpressionType.ValueType];
|
|
880
|
+
returnType = FunctionExpressionType.ValueType;
|
|
881
|
+
call(value) {
|
|
882
|
+
if (isArray(value) || isString(value)) return value.length;
|
|
883
|
+
if (isObject(value)) return Object.keys(value).length;
|
|
884
|
+
return Nothing;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* A Least Recently Used cache, implemented as an extended Map.
|
|
890
|
+
*/
|
|
891
|
+
class LRUCache extends Map {
|
|
892
|
+
constructor() {
|
|
893
|
+
let maxSize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 128;
|
|
894
|
+
let entries = arguments.length > 1 ? arguments[1] : undefined;
|
|
895
|
+
if (entries !== undefined) {
|
|
896
|
+
super(entries);
|
|
897
|
+
} else {
|
|
898
|
+
super();
|
|
899
|
+
}
|
|
900
|
+
this.maxSize = maxSize;
|
|
901
|
+
}
|
|
902
|
+
get(key) {
|
|
903
|
+
const val = super.get(key);
|
|
904
|
+
if (this.has(key)) {
|
|
905
|
+
this.delete(key);
|
|
906
|
+
this.set(key, val);
|
|
907
|
+
}
|
|
908
|
+
return val;
|
|
909
|
+
}
|
|
910
|
+
set(key, value) {
|
|
911
|
+
if (this.has(key)) {
|
|
912
|
+
this.delete(key);
|
|
913
|
+
} else if (this.size >= this.maxSize) {
|
|
914
|
+
this.delete(this.first());
|
|
915
|
+
}
|
|
916
|
+
return super.set(key, value);
|
|
917
|
+
}
|
|
918
|
+
first() {
|
|
919
|
+
return this.keys().next().value;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
class Match {
|
|
924
|
+
argTypes = [FunctionExpressionType.ValueType, FunctionExpressionType.ValueType];
|
|
925
|
+
returnType = FunctionExpressionType.LogicalType;
|
|
926
|
+
#cache;
|
|
927
|
+
constructor() {
|
|
928
|
+
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
929
|
+
this.options = options;
|
|
930
|
+
this.cacheSize = options.cacheSize ?? 10;
|
|
931
|
+
this.throwErrors = options.throwErrors ?? false;
|
|
932
|
+
this.#cache = new LRUCache(this.cacheSize);
|
|
933
|
+
}
|
|
934
|
+
call(s, pattern) {
|
|
935
|
+
if (this.cacheSize > 0) {
|
|
936
|
+
const re = this.#cache.get(pattern);
|
|
937
|
+
if (re) {
|
|
938
|
+
try {
|
|
939
|
+
return re.test(s);
|
|
940
|
+
} catch (error) {
|
|
941
|
+
if (this.throwErrors) throw error;
|
|
942
|
+
return false;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
try {
|
|
947
|
+
const re = new RegExp(this.fullMatch(pattern), "u");
|
|
948
|
+
if (this.cacheSize > 0) this.#cache.set(pattern, re);
|
|
949
|
+
return re.test(s);
|
|
950
|
+
} catch (error) {
|
|
951
|
+
if (this.throwErrors) throw error;
|
|
952
|
+
return false;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
fullMatch(pattern) {
|
|
956
|
+
const parts = [];
|
|
957
|
+
if (!pattern.startsWith("^")) parts.push("^");
|
|
958
|
+
parts.push(pattern);
|
|
959
|
+
if (!pattern.endsWith("$")) parts.push("$");
|
|
960
|
+
return parts.join("");
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
class Search {
|
|
965
|
+
argTypes = [FunctionExpressionType.ValueType, FunctionExpressionType.ValueType];
|
|
966
|
+
returnType = FunctionExpressionType.LogicalType;
|
|
967
|
+
#cache;
|
|
968
|
+
constructor() {
|
|
969
|
+
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
970
|
+
this.options = options;
|
|
971
|
+
this.cacheSize = options.cacheSize ?? 10;
|
|
972
|
+
this.throwErrors = options.throwErrors ?? false;
|
|
973
|
+
this.#cache = new LRUCache(this.cacheSize);
|
|
974
|
+
}
|
|
975
|
+
call(s, pattern) {
|
|
976
|
+
if (this.cacheSize > 0) {
|
|
977
|
+
const re = this.#cache.get(pattern);
|
|
978
|
+
if (re) {
|
|
979
|
+
try {
|
|
980
|
+
return !!s.match(re);
|
|
981
|
+
} catch (error) {
|
|
982
|
+
if (this.throwErrors) throw error;
|
|
983
|
+
return false;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
try {
|
|
988
|
+
const re = new RegExp(pattern, "u");
|
|
989
|
+
if (this.cacheSize > 0) this.#cache.set(pattern, re);
|
|
990
|
+
return !!s.match(re);
|
|
991
|
+
} catch (error) {
|
|
992
|
+
if (this.throwErrors) throw error;
|
|
993
|
+
return false;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
class Value {
|
|
999
|
+
argTypes = [FunctionExpressionType.NodesType];
|
|
1000
|
+
returnType = FunctionExpressionType.ValueType;
|
|
1001
|
+
call(nodes) {
|
|
1002
|
+
if (nodes.length === 1) return nodes.nodes[0].value;
|
|
1003
|
+
return Nothing;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
*
|
|
1009
|
+
*/
|
|
1010
|
+
let TokenKind = /*#__PURE__*/function (TokenKind) {
|
|
1011
|
+
TokenKind["AND"] = "TOKEN_AND";
|
|
1012
|
+
TokenKind["COLON"] = "TOKEN_COLON";
|
|
1013
|
+
TokenKind["COMMA"] = "TOKEN_COMMA";
|
|
1014
|
+
TokenKind["CURRENT"] = "TOKEN_CURRENT_NODE";
|
|
1015
|
+
TokenKind["DDOT"] = "TOKEN_DDOT";
|
|
1016
|
+
TokenKind["DOT"] = "TOKEN_DOT";
|
|
1017
|
+
TokenKind["DOUBLE_QUOTE_STRING"] = "TOKEN_DOUBLE_QUOTE_STRING";
|
|
1018
|
+
TokenKind["EOF"] = "TOKEN_EOF";
|
|
1019
|
+
TokenKind["EQ"] = "TOKEN_EQ";
|
|
1020
|
+
TokenKind["ERROR"] = "TOKEN_ERROR";
|
|
1021
|
+
TokenKind["FALSE"] = "TOKEN_FALSE";
|
|
1022
|
+
TokenKind["FILTER"] = "TOKEN_FILTER_START";
|
|
1023
|
+
TokenKind["FUNCTION"] = "TOKEN_FUNCTION";
|
|
1024
|
+
TokenKind["GE"] = "TOKEN_GE";
|
|
1025
|
+
TokenKind["GT"] = "TOKEN_GT";
|
|
1026
|
+
TokenKind["INDEX"] = "TOKEN_INDEX";
|
|
1027
|
+
TokenKind["LBRACKET"] = "TOKEN_LBRACKET";
|
|
1028
|
+
TokenKind["LE"] = "TOKEN_LE";
|
|
1029
|
+
TokenKind["LG"] = "TOKEN_LG";
|
|
1030
|
+
TokenKind["LPAREN"] = "TOKEN_LPAREN";
|
|
1031
|
+
TokenKind["LT"] = "TOKEN_LT";
|
|
1032
|
+
TokenKind["NAME"] = "TOKEN_NAME";
|
|
1033
|
+
TokenKind["NE"] = "TOKEN_NE";
|
|
1034
|
+
TokenKind["NOT"] = "TOKEN_NOT";
|
|
1035
|
+
TokenKind["NULL"] = "TOKEN_NULL";
|
|
1036
|
+
TokenKind["NUMBER"] = "NUMBER";
|
|
1037
|
+
TokenKind["OR"] = "TOKEN_OR";
|
|
1038
|
+
TokenKind["RBRACKET"] = "TOKEN_RBRACKET";
|
|
1039
|
+
TokenKind["ROOT"] = "TOKEN_ROOT";
|
|
1040
|
+
TokenKind["RPAREN"] = "TOKEN_RPAREN";
|
|
1041
|
+
TokenKind["SINGLE_QUOTE_STRING"] = "TOKEN_SINGLE_QUOTE_STRING";
|
|
1042
|
+
TokenKind["TRUE"] = "TOKEN_TRUE";
|
|
1043
|
+
TokenKind["WILD"] = "TOKEN_WILD";
|
|
1044
|
+
return TokenKind;
|
|
1045
|
+
}({});
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
*
|
|
1049
|
+
*/
|
|
1050
|
+
class Token {
|
|
1051
|
+
constructor(kind, value, index, input) {
|
|
1052
|
+
this.kind = kind;
|
|
1053
|
+
this.value = value;
|
|
1054
|
+
this.index = index;
|
|
1055
|
+
this.input = input;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
new Token(TokenKind.EOF, "", -1, "");
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
*
|
|
1062
|
+
*/
|
|
1063
|
+
class TokenStream {
|
|
1064
|
+
#pos = 0;
|
|
1065
|
+
constructor(tokens) {
|
|
1066
|
+
this.tokens = tokens;
|
|
1067
|
+
}
|
|
1068
|
+
get current() {
|
|
1069
|
+
return this.tokens[this.#pos];
|
|
1070
|
+
}
|
|
1071
|
+
get peek() {
|
|
1072
|
+
if (this.#pos >= this.tokens.length - 1) return this.tokens[this.tokens.length - 1];
|
|
1073
|
+
return this.tokens[this.#pos + 1];
|
|
1074
|
+
}
|
|
1075
|
+
next() {
|
|
1076
|
+
const current = this.current;
|
|
1077
|
+
this.#pos += 1;
|
|
1078
|
+
return current;
|
|
1079
|
+
}
|
|
1080
|
+
backup() {
|
|
1081
|
+
if (this.#pos > 0) this.#pos -= 1;
|
|
1082
|
+
}
|
|
1083
|
+
expect(kind) {
|
|
1084
|
+
if (this.current.kind !== kind) {
|
|
1085
|
+
throw new JSONPathSyntaxError(`expected token '${kind}', found '${this.current.kind}'`, this.current);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
expectPeek(kind) {
|
|
1089
|
+
const peeked = this.peek;
|
|
1090
|
+
if (peeked.kind !== kind) {
|
|
1091
|
+
throw new JSONPathSyntaxError(`expected token '${kind}', found '${peeked.kind}'`, peeked);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// These regular expressions are to be used with Lexer.acceptMatchRun(),
|
|
1097
|
+
// which expects the sticky flag to be set.
|
|
1098
|
+
const exponentPattern = /e[+-]\d+/y;
|
|
1099
|
+
const functionNamePattern = /[a-z][a-z_0-9]*/y;
|
|
1100
|
+
const indexPattern = /-?\d+/y;
|
|
1101
|
+
const intPattern = /-?[0-9]+/y;
|
|
1102
|
+
const namePattern = /[\u0080-\uFFFFa-zA-Z_][\u0080-\uFFFFa-zA-Z0-9_-]*/y;
|
|
1103
|
+
const whitespace = new Set([" ", "\n", "\t", "\r"]);
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* JSONPath lexical scanner.
|
|
1107
|
+
*
|
|
1108
|
+
* Lexer state is shared between this class and the current state function. A
|
|
1109
|
+
* new _Lexer_ instance is automatically created every time a path is tokenized.
|
|
1110
|
+
*
|
|
1111
|
+
* Use {@link tokenize} to get an array of {@link Token}'s for a JSONPath query.
|
|
1112
|
+
*/
|
|
1113
|
+
class Lexer {
|
|
1114
|
+
/**
|
|
1115
|
+
* Filter nesting level.
|
|
1116
|
+
*/
|
|
1117
|
+
filterLevel = 0;
|
|
1118
|
+
|
|
1119
|
+
/**
|
|
1120
|
+
* A running count of parentheses for each, possibly nested, function call.
|
|
1121
|
+
*
|
|
1122
|
+
* If the stack is empty, we are not in a function call. Remember that
|
|
1123
|
+
* function arguments can use arbitrarily nested in parentheses.
|
|
1124
|
+
*/
|
|
1125
|
+
parenStack = [];
|
|
1126
|
+
|
|
1127
|
+
/** Tokens resulting from tokenizing a JSONPath query. */
|
|
1128
|
+
tokens = [];
|
|
1129
|
+
#start = 0;
|
|
1130
|
+
#pos = 0;
|
|
1131
|
+
|
|
1132
|
+
/**
|
|
1133
|
+
* @param path - A JSONPath query.
|
|
1134
|
+
*/
|
|
1135
|
+
constructor(path) {
|
|
1136
|
+
this.path = path;
|
|
1137
|
+
}
|
|
1138
|
+
get pos() {
|
|
1139
|
+
return this.#pos;
|
|
1140
|
+
}
|
|
1141
|
+
get start() {
|
|
1142
|
+
return this.#start;
|
|
1143
|
+
}
|
|
1144
|
+
run() {
|
|
1145
|
+
let state = lexRoot;
|
|
1146
|
+
while (state) {
|
|
1147
|
+
state = state(this);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
emit(t) {
|
|
1151
|
+
this.tokens.push(new Token(t, this.path.slice(this.#start, this.#pos), this.#start, this.path));
|
|
1152
|
+
this.#start = this.#pos;
|
|
1153
|
+
}
|
|
1154
|
+
next() {
|
|
1155
|
+
if (this.#pos >= this.path.length) return "";
|
|
1156
|
+
const s = this.path[this.#pos];
|
|
1157
|
+
this.#pos += 1;
|
|
1158
|
+
return s;
|
|
1159
|
+
}
|
|
1160
|
+
ignore() {
|
|
1161
|
+
this.#start = this.#pos;
|
|
1162
|
+
}
|
|
1163
|
+
backup() {
|
|
1164
|
+
if (this.#pos <= this.#start) {
|
|
1165
|
+
const msg = "can't backup beyond start";
|
|
1166
|
+
throw new JSONPathLexerError(msg, new Token(TokenKind.ERROR, msg, this.#pos, this.path));
|
|
1167
|
+
}
|
|
1168
|
+
this.#pos -= 1;
|
|
1169
|
+
}
|
|
1170
|
+
peek() {
|
|
1171
|
+
const ch = this.next();
|
|
1172
|
+
if (ch) this.backup();
|
|
1173
|
+
return ch;
|
|
1174
|
+
}
|
|
1175
|
+
accept(valid) {
|
|
1176
|
+
const ch = this.next();
|
|
1177
|
+
if (valid.has(ch)) return true;
|
|
1178
|
+
if (ch) this.backup();
|
|
1179
|
+
return false;
|
|
1180
|
+
}
|
|
1181
|
+
acceptMatch(pattern) {
|
|
1182
|
+
const ch = this.next();
|
|
1183
|
+
if (pattern.test(ch)) return true;
|
|
1184
|
+
if (ch) this.backup();
|
|
1185
|
+
return false;
|
|
1186
|
+
}
|
|
1187
|
+
acceptRun(valid) {
|
|
1188
|
+
let found = false;
|
|
1189
|
+
let ch = this.next();
|
|
1190
|
+
while (valid.has(ch)) {
|
|
1191
|
+
ch = this.next();
|
|
1192
|
+
found = true;
|
|
1193
|
+
}
|
|
1194
|
+
if (ch) this.backup();
|
|
1195
|
+
return found;
|
|
1196
|
+
}
|
|
1197
|
+
acceptMatchRun(pattern) {
|
|
1198
|
+
pattern.lastIndex = this.#pos;
|
|
1199
|
+
const match = pattern.exec(this.path);
|
|
1200
|
+
pattern.lastIndex = 0;
|
|
1201
|
+
if (match) {
|
|
1202
|
+
this.#pos += match[0].length;
|
|
1203
|
+
return true;
|
|
1204
|
+
}
|
|
1205
|
+
return false;
|
|
1206
|
+
}
|
|
1207
|
+
ignoreWhitespace() {
|
|
1208
|
+
if (this.#pos !== this.#start) {
|
|
1209
|
+
const msg = `must emit or ignore before consuming whitespace ('${this.path.slice(this.#start, this.#pos)}':${this.pos})`;
|
|
1210
|
+
throw new JSONPathLexerError(msg, new Token(TokenKind.ERROR, msg, this.pos, this.path));
|
|
1211
|
+
}
|
|
1212
|
+
if (this.acceptRun(whitespace)) {
|
|
1213
|
+
this.ignore();
|
|
1214
|
+
return true;
|
|
1215
|
+
}
|
|
1216
|
+
return false;
|
|
1217
|
+
}
|
|
1218
|
+
error(msg) {
|
|
1219
|
+
this.tokens.push(new Token(TokenKind.ERROR, msg, this.#pos, this.path));
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Return a lexer for _path_ and an array to be populated with Tokens.
|
|
1224
|
+
*
|
|
1225
|
+
* `lexer.run()` must be called to populate the returned tokens array.
|
|
1226
|
+
*
|
|
1227
|
+
* You probably want to use {@link tokenize} instead of _lex_. This function
|
|
1228
|
+
* is mostly for internal use, where we want to test the state of the returned
|
|
1229
|
+
* _lexer_ after tokens have been populated.
|
|
1230
|
+
*
|
|
1231
|
+
* @param path - A JSONPath query.
|
|
1232
|
+
* @returns A two-tuple containing a lexer for _path_ and an array to populate
|
|
1233
|
+
* with tokens.
|
|
1234
|
+
*/
|
|
1235
|
+
function lex(path) {
|
|
1236
|
+
const lexer = new Lexer(path);
|
|
1237
|
+
return [lexer, lexer.tokens];
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
/**
|
|
1241
|
+
* Scan _path_ and return an array of tokens to be parsed by the parser.
|
|
1242
|
+
* @param path - A JSONPath query.
|
|
1243
|
+
* @returns Tokens to be parsed by the parser.
|
|
1244
|
+
*/
|
|
1245
|
+
function tokenize(path) {
|
|
1246
|
+
const [lexer, tokens] = lex(path);
|
|
1247
|
+
lexer.run();
|
|
1248
|
+
if (tokens.length && tokens[tokens.length - 1].kind === TokenKind.ERROR) {
|
|
1249
|
+
throw new JSONPathSyntaxError(tokens[tokens.length - 1].value, tokens[tokens.length - 1]);
|
|
1250
|
+
}
|
|
1251
|
+
return tokens;
|
|
1252
|
+
}
|
|
1253
|
+
function lexRoot(l) {
|
|
1254
|
+
const ch = l.next();
|
|
1255
|
+
if (ch !== "$") {
|
|
1256
|
+
l.backup();
|
|
1257
|
+
l.error(`expected '$', found '${ch}'`);
|
|
1258
|
+
return null;
|
|
1259
|
+
}
|
|
1260
|
+
l.emit(TokenKind.ROOT);
|
|
1261
|
+
return lexSegment;
|
|
1262
|
+
}
|
|
1263
|
+
function lexSegment(l) {
|
|
1264
|
+
if (l.ignoreWhitespace() && !l.peek()) {
|
|
1265
|
+
l.error("trailing whitespace");
|
|
1266
|
+
}
|
|
1267
|
+
const ch = l.next();
|
|
1268
|
+
switch (ch) {
|
|
1269
|
+
case "":
|
|
1270
|
+
l.emit(TokenKind.EOF);
|
|
1271
|
+
return null;
|
|
1272
|
+
case ".":
|
|
1273
|
+
if (l.peek() === ".") {
|
|
1274
|
+
l.next();
|
|
1275
|
+
l.emit(TokenKind.DDOT);
|
|
1276
|
+
return lexDescendantSelection;
|
|
1277
|
+
}
|
|
1278
|
+
return lexDotSelector;
|
|
1279
|
+
case "[":
|
|
1280
|
+
l.emit(TokenKind.LBRACKET);
|
|
1281
|
+
return lexInsideBracketedSelection;
|
|
1282
|
+
default:
|
|
1283
|
+
l.backup();
|
|
1284
|
+
if (l.filterLevel) return lexInsideFilter;
|
|
1285
|
+
l.error(`expected '.', '..' or a bracketed selection, found '${ch}'`);
|
|
1286
|
+
return null;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* Similar to _lexSegment_, but ..
|
|
1292
|
+
* - no leading whitespace
|
|
1293
|
+
* - no extra dot before a property name
|
|
1294
|
+
* - there must be a selector, so EOF would be an error
|
|
1295
|
+
* @param l -
|
|
1296
|
+
* @returns -
|
|
1297
|
+
*/
|
|
1298
|
+
function lexDescendantSelection(l) {
|
|
1299
|
+
const ch = l.next();
|
|
1300
|
+
switch (ch) {
|
|
1301
|
+
case "":
|
|
1302
|
+
l.error("bald descendant segment");
|
|
1303
|
+
return null;
|
|
1304
|
+
case "*":
|
|
1305
|
+
l.emit(TokenKind.WILD);
|
|
1306
|
+
return lexSegment;
|
|
1307
|
+
case "[":
|
|
1308
|
+
l.emit(TokenKind.LBRACKET);
|
|
1309
|
+
return lexInsideBracketedSelection;
|
|
1310
|
+
default:
|
|
1311
|
+
l.backup();
|
|
1312
|
+
if (l.acceptMatchRun(namePattern)) {
|
|
1313
|
+
l.emit(TokenKind.NAME);
|
|
1314
|
+
return lexSegment;
|
|
1315
|
+
} else {
|
|
1316
|
+
l.error(`unexpected descendent selection token '${ch}'`);
|
|
1317
|
+
return null;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
function lexDotSelector(l) {
|
|
1322
|
+
l.ignore();
|
|
1323
|
+
if (l.ignoreWhitespace()) {
|
|
1324
|
+
l.error("unexpected whitespace after dot");
|
|
1325
|
+
return null;
|
|
1326
|
+
}
|
|
1327
|
+
const ch = l.next();
|
|
1328
|
+
if (ch === "*") {
|
|
1329
|
+
l.emit(TokenKind.WILD);
|
|
1330
|
+
return lexSegment;
|
|
1331
|
+
}
|
|
1332
|
+
l.backup();
|
|
1333
|
+
if (l.acceptMatchRun(namePattern)) {
|
|
1334
|
+
l.emit(TokenKind.NAME);
|
|
1335
|
+
return lexSegment;
|
|
1336
|
+
} else {
|
|
1337
|
+
l.error(`unexpected shorthand selector '${ch}'`);
|
|
1338
|
+
return null;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
function lexInsideBracketedSelection(l) {
|
|
1342
|
+
for (;;) {
|
|
1343
|
+
l.ignoreWhitespace();
|
|
1344
|
+
const ch = l.next();
|
|
1345
|
+
switch (ch) {
|
|
1346
|
+
case "]":
|
|
1347
|
+
l.emit(TokenKind.RBRACKET);
|
|
1348
|
+
if (l.filterLevel) return lexInsideFilter;
|
|
1349
|
+
return lexSegment;
|
|
1350
|
+
case "":
|
|
1351
|
+
l.error("unclosed bracketed selection");
|
|
1352
|
+
return null;
|
|
1353
|
+
case "*":
|
|
1354
|
+
l.emit(TokenKind.WILD);
|
|
1355
|
+
continue;
|
|
1356
|
+
case "?":
|
|
1357
|
+
l.emit(TokenKind.FILTER);
|
|
1358
|
+
l.filterLevel += 1;
|
|
1359
|
+
return lexInsideFilter;
|
|
1360
|
+
case ",":
|
|
1361
|
+
l.emit(TokenKind.COMMA);
|
|
1362
|
+
continue;
|
|
1363
|
+
case ":":
|
|
1364
|
+
l.emit(TokenKind.COLON);
|
|
1365
|
+
continue;
|
|
1366
|
+
case "'":
|
|
1367
|
+
return lexSingleQuoteStringInsideBracketSelection;
|
|
1368
|
+
case '"':
|
|
1369
|
+
return lexDoubleQuoteStringInsideBracketSelection;
|
|
1370
|
+
default:
|
|
1371
|
+
l.backup();
|
|
1372
|
+
if (l.acceptMatchRun(indexPattern)) {
|
|
1373
|
+
l.emit(TokenKind.INDEX);
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1376
|
+
l.error(`unexpected token '${ch}' in bracketed selection`);
|
|
1377
|
+
return null;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
1383
|
+
function lexInsideFilter(l) {
|
|
1384
|
+
for (;;) {
|
|
1385
|
+
l.ignoreWhitespace();
|
|
1386
|
+
const ch = l.next();
|
|
1387
|
+
switch (ch) {
|
|
1388
|
+
case "":
|
|
1389
|
+
case "]":
|
|
1390
|
+
l.filterLevel -= 1;
|
|
1391
|
+
l.backup();
|
|
1392
|
+
return lexInsideBracketedSelection;
|
|
1393
|
+
case ",":
|
|
1394
|
+
l.emit(TokenKind.COMMA);
|
|
1395
|
+
// If we have unbalanced parens, we are inside a function call and a
|
|
1396
|
+
// comma separates arguments. Otherwise a comma separates selectors.
|
|
1397
|
+
if (l.parenStack.length) continue;
|
|
1398
|
+
l.filterLevel -= 1;
|
|
1399
|
+
return lexInsideBracketedSelection;
|
|
1400
|
+
case "'":
|
|
1401
|
+
return lexSingleQuoteStringInsideFilterExpression;
|
|
1402
|
+
case '"':
|
|
1403
|
+
return lexDoubleQuoteStringInsideFilterExpression;
|
|
1404
|
+
case "(":
|
|
1405
|
+
l.emit(TokenKind.LPAREN);
|
|
1406
|
+
// Are we in a function call? If so, a function argument contains parens.
|
|
1407
|
+
if (l.parenStack.length) l.parenStack[l.parenStack.length - 1] += 1;
|
|
1408
|
+
continue;
|
|
1409
|
+
case ")":
|
|
1410
|
+
l.emit(TokenKind.RPAREN);
|
|
1411
|
+
// Are we closing a function call or a parenthesized expression?
|
|
1412
|
+
if (l.parenStack.length) {
|
|
1413
|
+
if (l.parenStack[l.parenStack.length - 1] === 1) {
|
|
1414
|
+
l.parenStack.pop();
|
|
1415
|
+
} else {
|
|
1416
|
+
l.parenStack[l.parenStack.length - 1] -= 1;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
continue;
|
|
1420
|
+
case "$":
|
|
1421
|
+
l.emit(TokenKind.ROOT);
|
|
1422
|
+
return lexSegment;
|
|
1423
|
+
case "@":
|
|
1424
|
+
l.emit(TokenKind.CURRENT);
|
|
1425
|
+
return lexSegment;
|
|
1426
|
+
case ".":
|
|
1427
|
+
l.backup();
|
|
1428
|
+
return lexSegment;
|
|
1429
|
+
case "!":
|
|
1430
|
+
if (l.peek() === "=") {
|
|
1431
|
+
l.next();
|
|
1432
|
+
l.emit(TokenKind.NE);
|
|
1433
|
+
} else {
|
|
1434
|
+
l.emit(TokenKind.NOT);
|
|
1435
|
+
}
|
|
1436
|
+
continue;
|
|
1437
|
+
case "=":
|
|
1438
|
+
if (l.peek() === "=") {
|
|
1439
|
+
l.next();
|
|
1440
|
+
l.emit(TokenKind.EQ);
|
|
1441
|
+
continue;
|
|
1442
|
+
} else {
|
|
1443
|
+
l.backup();
|
|
1444
|
+
l.error(`unexpected filter selector token '${ch}'`);
|
|
1445
|
+
return null;
|
|
1446
|
+
}
|
|
1447
|
+
case "<":
|
|
1448
|
+
if (l.peek() === "=") {
|
|
1449
|
+
l.next();
|
|
1450
|
+
l.emit(TokenKind.LE);
|
|
1451
|
+
} else {
|
|
1452
|
+
l.emit(TokenKind.LT);
|
|
1453
|
+
}
|
|
1454
|
+
continue;
|
|
1455
|
+
case ">":
|
|
1456
|
+
if (l.peek() === "=") {
|
|
1457
|
+
l.next();
|
|
1458
|
+
l.emit(TokenKind.GE);
|
|
1459
|
+
} else {
|
|
1460
|
+
l.emit(TokenKind.GT);
|
|
1461
|
+
}
|
|
1462
|
+
continue;
|
|
1463
|
+
default:
|
|
1464
|
+
l.backup();
|
|
1465
|
+
|
|
1466
|
+
// numbers
|
|
1467
|
+
if (l.acceptMatchRun(intPattern)) {
|
|
1468
|
+
if (l.peek() === ".") {
|
|
1469
|
+
// A float.
|
|
1470
|
+
l.next();
|
|
1471
|
+
if (!l.acceptMatchRun(intPattern)) {
|
|
1472
|
+
// Need at least one digit after a decimal place.
|
|
1473
|
+
l.error("a fractional digit is required after a decimal point");
|
|
1474
|
+
return null;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
l.acceptMatchRun(exponentPattern);
|
|
1478
|
+
l.emit(TokenKind.NUMBER);
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
if (l.acceptMatchRun(/&&/y)) {
|
|
1482
|
+
l.emit(TokenKind.AND);
|
|
1483
|
+
continue;
|
|
1484
|
+
}
|
|
1485
|
+
if (l.acceptMatchRun(/\|\|/y)) {
|
|
1486
|
+
l.emit(TokenKind.OR);
|
|
1487
|
+
continue;
|
|
1488
|
+
}
|
|
1489
|
+
if (l.acceptMatchRun(/true/y)) {
|
|
1490
|
+
l.emit(TokenKind.TRUE);
|
|
1491
|
+
continue;
|
|
1492
|
+
}
|
|
1493
|
+
if (l.acceptMatchRun(/false/y)) {
|
|
1494
|
+
l.emit(TokenKind.FALSE);
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1497
|
+
if (l.acceptMatchRun(/null/y)) {
|
|
1498
|
+
l.emit(TokenKind.NULL);
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// functions
|
|
1503
|
+
if (l.acceptMatchRun(functionNamePattern) && l.peek() === "(") {
|
|
1504
|
+
// Keep track of parentheses for this function call.
|
|
1505
|
+
l.parenStack.push(1);
|
|
1506
|
+
l.emit(TokenKind.FUNCTION);
|
|
1507
|
+
l.next();
|
|
1508
|
+
l.ignore();
|
|
1509
|
+
continue;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
l.error(`unexpected filter selector token '${ch}'`);
|
|
1513
|
+
return null;
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
/**
|
|
1518
|
+
* Return a state function tokenizing string literals using _quote_ and
|
|
1519
|
+
* returning control to _state_.
|
|
1520
|
+
* @param quote - One of `'` or `"`.
|
|
1521
|
+
* @param state - The state function to return control to.
|
|
1522
|
+
* @returns String tokenizing state function.
|
|
1523
|
+
*/
|
|
1524
|
+
function makeLexString(quote, state) {
|
|
1525
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
1526
|
+
function _lexString(l) {
|
|
1527
|
+
l.ignore();
|
|
1528
|
+
if (l.peek() === quote) {
|
|
1529
|
+
// empty string
|
|
1530
|
+
l.emit(quote === "'" ? TokenKind.SINGLE_QUOTE_STRING : TokenKind.DOUBLE_QUOTE_STRING);
|
|
1531
|
+
l.next();
|
|
1532
|
+
l.ignore();
|
|
1533
|
+
return state;
|
|
1534
|
+
}
|
|
1535
|
+
for (;;) {
|
|
1536
|
+
const la = l.path.slice(l.pos, l.pos + 2);
|
|
1537
|
+
const ch = l.next();
|
|
1538
|
+
if (la === "\\\\" || la === `\\${quote}`) {
|
|
1539
|
+
l.next();
|
|
1540
|
+
continue;
|
|
1541
|
+
} else if (ch === "\\" && !la.match(/\\[bfnrtu/]/)) {
|
|
1542
|
+
l.error(`invalid escape`);
|
|
1543
|
+
return null;
|
|
1544
|
+
}
|
|
1545
|
+
if (!ch) {
|
|
1546
|
+
l.error(`unclosed string starting at index ${l.start}`);
|
|
1547
|
+
return null;
|
|
1548
|
+
}
|
|
1549
|
+
if (ch === quote) {
|
|
1550
|
+
l.backup();
|
|
1551
|
+
l.emit(quote === "'" ? TokenKind.SINGLE_QUOTE_STRING : TokenKind.DOUBLE_QUOTE_STRING);
|
|
1552
|
+
l.next();
|
|
1553
|
+
l.ignore();
|
|
1554
|
+
return state;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
return _lexString;
|
|
1559
|
+
}
|
|
1560
|
+
const lexSingleQuoteStringInsideBracketSelection = makeLexString("'", lexInsideBracketedSelection);
|
|
1561
|
+
const lexDoubleQuoteStringInsideBracketSelection = makeLexString('"', lexInsideBracketedSelection);
|
|
1562
|
+
const lexSingleQuoteStringInsideFilterExpression = makeLexString("'", lexInsideFilter);
|
|
1563
|
+
const lexDoubleQuoteStringInsideFilterExpression = makeLexString('"', lexInsideFilter);
|
|
1564
|
+
|
|
1565
|
+
/**
|
|
1566
|
+
* Base class for all JSONPath segments and selectors.
|
|
1567
|
+
*/
|
|
1568
|
+
class JSONPathSelector {
|
|
1569
|
+
/**
|
|
1570
|
+
* @param token - The token at the start of this selector.
|
|
1571
|
+
*/
|
|
1572
|
+
constructor(environment, token) {
|
|
1573
|
+
this.environment = environment;
|
|
1574
|
+
this.token = token;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
/**
|
|
1578
|
+
* @param nodes - Nodes matched by preceding selectors.
|
|
1579
|
+
*/
|
|
1580
|
+
|
|
1581
|
+
/**
|
|
1582
|
+
* Return a canonical string representation of this selector.
|
|
1583
|
+
*/
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
/**
|
|
1587
|
+
* Shorthand and quoted name selector.
|
|
1588
|
+
*/
|
|
1589
|
+
class NameSelector extends JSONPathSelector {
|
|
1590
|
+
constructor(environment, token, name, shorthand) {
|
|
1591
|
+
super(environment, token);
|
|
1592
|
+
this.environment = environment;
|
|
1593
|
+
this.token = token;
|
|
1594
|
+
this.name = name;
|
|
1595
|
+
this.shorthand = shorthand;
|
|
1596
|
+
}
|
|
1597
|
+
resolve(nodes) {
|
|
1598
|
+
const rv = [];
|
|
1599
|
+
for (const node of nodes) {
|
|
1600
|
+
if (hasStringKey(node.value, this.name)) {
|
|
1601
|
+
rv.push(new JSONPathNode(node.value[this.name], node.location.concat(this.name), node.root));
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
return new JSONPathNodeList(rv);
|
|
1605
|
+
}
|
|
1606
|
+
toString() {
|
|
1607
|
+
return this.shorthand ? `['${this.name}']` : `'${this.name}'`;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
/**
|
|
1612
|
+
* Array index selector.
|
|
1613
|
+
*/
|
|
1614
|
+
class IndexSelector extends JSONPathSelector {
|
|
1615
|
+
constructor(environment, token, index) {
|
|
1616
|
+
super(environment, token);
|
|
1617
|
+
this.environment = environment;
|
|
1618
|
+
this.token = token;
|
|
1619
|
+
this.index = index;
|
|
1620
|
+
if (index < this.environment.options.minIntIndex || index > this.environment.options.maxIntIndex) {
|
|
1621
|
+
throw new JSONPathIndexError("index out of range", this.token);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
resolve(nodes) {
|
|
1625
|
+
const rv = [];
|
|
1626
|
+
for (const node of nodes) {
|
|
1627
|
+
if (isArray(node.value)) {
|
|
1628
|
+
const normIndex = this.normalizedIndex(node.value.length);
|
|
1629
|
+
if (normIndex in node.value) {
|
|
1630
|
+
rv.push(new JSONPathNode(node.value[normIndex], node.location.concat(normIndex), node.root));
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
return new JSONPathNodeList(rv);
|
|
1635
|
+
}
|
|
1636
|
+
toString() {
|
|
1637
|
+
return String(this.index);
|
|
1638
|
+
}
|
|
1639
|
+
normalizedIndex(length) {
|
|
1640
|
+
if (this.index < 0 && length >= Math.abs(this.index)) return length + this.index;
|
|
1641
|
+
return this.index;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
class SliceSelector extends JSONPathSelector {
|
|
1645
|
+
constructor(environment, token, start, stop, step) {
|
|
1646
|
+
super(environment, token);
|
|
1647
|
+
this.environment = environment;
|
|
1648
|
+
this.token = token;
|
|
1649
|
+
this.start = start;
|
|
1650
|
+
this.stop = stop;
|
|
1651
|
+
this.step = step;
|
|
1652
|
+
this.checkRange(start, stop, step);
|
|
1653
|
+
}
|
|
1654
|
+
resolve(nodes) {
|
|
1655
|
+
const rv = [];
|
|
1656
|
+
for (const node of nodes) {
|
|
1657
|
+
if (!isArray(node.value)) continue;
|
|
1658
|
+
for (const [i, value] of this.slice(node.value, this.start, this.stop, this.step)) {
|
|
1659
|
+
rv.push(new JSONPathNode(value, node.location.concat(i), node.root));
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
return new JSONPathNodeList(rv);
|
|
1663
|
+
}
|
|
1664
|
+
toString() {
|
|
1665
|
+
const start = this.start ? this.start : "";
|
|
1666
|
+
const stop = this.stop ? this.stop : "";
|
|
1667
|
+
const step = this.step ? this.step : "1";
|
|
1668
|
+
return `${start}:${stop}:${step}`;
|
|
1669
|
+
}
|
|
1670
|
+
checkRange() {
|
|
1671
|
+
for (var _len = arguments.length, indices = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
1672
|
+
indices[_key] = arguments[_key];
|
|
1673
|
+
}
|
|
1674
|
+
for (const index of indices) {
|
|
1675
|
+
if (index !== undefined && (index < this.environment.options.minIntIndex || index > this.environment.options.maxIntIndex)) {
|
|
1676
|
+
throw new JSONPathIndexError("index out of range", this.token);
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
normalizedIndex(length, index) {
|
|
1681
|
+
if (index < 0 && length >= Math.abs(index)) return Math.min(length + index, length - 1);
|
|
1682
|
+
return Math.min(index, length - 1);
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
1686
|
+
slice(arr, start, stop, step) {
|
|
1687
|
+
if (!arr.length) return [];
|
|
1688
|
+
|
|
1689
|
+
// Handle negative start and stop values
|
|
1690
|
+
if (start === undefined || start === null) {
|
|
1691
|
+
start = step && step < 0 ? arr.length - 1 : 0;
|
|
1692
|
+
} else if (start < 0) {
|
|
1693
|
+
start = Math.max(arr.length + start, 0);
|
|
1694
|
+
} else {
|
|
1695
|
+
start = Math.min(start, arr.length - 1);
|
|
1696
|
+
}
|
|
1697
|
+
if (stop === undefined || stop === null) {
|
|
1698
|
+
stop = step && step < 0 ? -1 : arr.length;
|
|
1699
|
+
} else if (stop < 0) {
|
|
1700
|
+
stop = Math.max(arr.length + stop, -1);
|
|
1701
|
+
} else {
|
|
1702
|
+
stop = Math.min(stop, arr.length);
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
// Handle step value
|
|
1706
|
+
if (step === 0) {
|
|
1707
|
+
return [];
|
|
1708
|
+
}
|
|
1709
|
+
if (!step) {
|
|
1710
|
+
step = 1;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
// Perform the slice
|
|
1714
|
+
const slicedArray = [];
|
|
1715
|
+
if (step > 0) {
|
|
1716
|
+
for (let i = start; i < stop; i += step) {
|
|
1717
|
+
slicedArray.push([i, arr[i]]);
|
|
1718
|
+
}
|
|
1719
|
+
} else {
|
|
1720
|
+
for (let i = start; i > stop; i += step) {
|
|
1721
|
+
slicedArray.push([i, arr[i]]);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
return slicedArray;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
class WildcardSelector extends JSONPathSelector {
|
|
1728
|
+
constructor(environment, token) {
|
|
1729
|
+
let shorthand = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
1730
|
+
super(environment, token);
|
|
1731
|
+
this.environment = environment;
|
|
1732
|
+
this.token = token;
|
|
1733
|
+
this.shorthand = shorthand;
|
|
1734
|
+
}
|
|
1735
|
+
resolve(nodes) {
|
|
1736
|
+
const rv = [];
|
|
1737
|
+
for (const node of nodes) {
|
|
1738
|
+
if (node.value instanceof String) continue;
|
|
1739
|
+
if (isArray(node.value)) {
|
|
1740
|
+
for (let i = 0; i < node.value.length; i++) {
|
|
1741
|
+
rv.push(new JSONPathNode(node.value[i], node.location.concat(i), node.root));
|
|
1742
|
+
}
|
|
1743
|
+
} else if (isObject(node.value)) {
|
|
1744
|
+
for (const [key, value] of Object.entries(node.value)) {
|
|
1745
|
+
rv.push(new JSONPathNode(value, node.location.concat(key), node.root));
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
return new JSONPathNodeList(rv);
|
|
1750
|
+
}
|
|
1751
|
+
toString() {
|
|
1752
|
+
return this.shorthand ? "[*]" : "*";
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
class RecursiveDescentSegment extends JSONPathSelector {
|
|
1756
|
+
resolve(nodes) {
|
|
1757
|
+
const rv = [];
|
|
1758
|
+
for (const node of nodes) {
|
|
1759
|
+
rv.push(node, ...this.visit(node));
|
|
1760
|
+
}
|
|
1761
|
+
return new JSONPathNodeList(rv);
|
|
1762
|
+
}
|
|
1763
|
+
toString() {
|
|
1764
|
+
return "..";
|
|
1765
|
+
}
|
|
1766
|
+
visit(node) {
|
|
1767
|
+
const rv = [];
|
|
1768
|
+
if (node.value instanceof String) return new JSONPathNodeList(rv);
|
|
1769
|
+
if (isArray(node.value)) {
|
|
1770
|
+
for (let i = 0; i < node.value.length; i++) {
|
|
1771
|
+
const _node = new JSONPathNode(node.value[i], node.location.concat(i), node.root);
|
|
1772
|
+
rv.push(_node, ...this.visit(_node));
|
|
1773
|
+
}
|
|
1774
|
+
} else if (isObject(node.value)) {
|
|
1775
|
+
for (const [key, value] of Object.entries(node.value)) {
|
|
1776
|
+
const _node = new JSONPathNode(value, node.location.concat(key), node.root);
|
|
1777
|
+
rv.push(_node, ...this.visit(_node));
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
return new JSONPathNodeList(rv);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
class FilterSelector extends JSONPathSelector {
|
|
1784
|
+
constructor(environment, token, expression) {
|
|
1785
|
+
super(environment, token);
|
|
1786
|
+
this.environment = environment;
|
|
1787
|
+
this.token = token;
|
|
1788
|
+
this.expression = expression;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
1792
|
+
resolve(nodes) {
|
|
1793
|
+
const rv = [];
|
|
1794
|
+
for (const node of nodes) {
|
|
1795
|
+
if (node.value instanceof String) continue;
|
|
1796
|
+
if (isArray(node.value)) {
|
|
1797
|
+
for (let i = 0; i < node.value.length; i++) {
|
|
1798
|
+
const value = node.value[i];
|
|
1799
|
+
const filterContext = {
|
|
1800
|
+
environment: this.environment,
|
|
1801
|
+
currentValue: value,
|
|
1802
|
+
rootValue: node.root
|
|
1803
|
+
};
|
|
1804
|
+
if (this.expression.evaluate(filterContext)) {
|
|
1805
|
+
rv.push(new JSONPathNode(value, node.location.concat(i), node.root));
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
} else if (isObject(node.value)) {
|
|
1809
|
+
for (const [key, value] of Object.entries(node.value)) {
|
|
1810
|
+
const filterContext = {
|
|
1811
|
+
environment: this.environment,
|
|
1812
|
+
currentValue: value,
|
|
1813
|
+
rootValue: node.root
|
|
1814
|
+
};
|
|
1815
|
+
if (this.expression.evaluate(filterContext)) {
|
|
1816
|
+
rv.push(new JSONPathNode(value, node.location.concat(key), node.root));
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
return new JSONPathNodeList(rv);
|
|
1822
|
+
}
|
|
1823
|
+
toString() {
|
|
1824
|
+
return `?${this.expression.toString()}`;
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
class BracketedSelection extends JSONPathSelector {
|
|
1828
|
+
constructor(environment, token, items) {
|
|
1829
|
+
super(environment, token);
|
|
1830
|
+
this.environment = environment;
|
|
1831
|
+
this.token = token;
|
|
1832
|
+
this.items = items;
|
|
1833
|
+
}
|
|
1834
|
+
resolve(nodes) {
|
|
1835
|
+
const rv = [];
|
|
1836
|
+
for (const node of nodes) {
|
|
1837
|
+
for (const item of this.items) {
|
|
1838
|
+
rv.push(...item.resolve(new JSONPathNodeList([node])));
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
return new JSONPathNodeList(rv);
|
|
1842
|
+
}
|
|
1843
|
+
toString() {
|
|
1844
|
+
return `[${this.items.map(itm => itm.toString()).join(", ")}]`;
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
var selectors = /*#__PURE__*/Object.freeze({
|
|
1849
|
+
__proto__: null,
|
|
1850
|
+
BracketedSelection: BracketedSelection,
|
|
1851
|
+
FilterSelector: FilterSelector,
|
|
1852
|
+
IndexSelector: IndexSelector,
|
|
1853
|
+
JSONPathSelector: JSONPathSelector,
|
|
1854
|
+
NameSelector: NameSelector,
|
|
1855
|
+
RecursiveDescentSegment: RecursiveDescentSegment,
|
|
1856
|
+
SliceSelector: SliceSelector,
|
|
1857
|
+
WildcardSelector: WildcardSelector
|
|
1858
|
+
});
|
|
1859
|
+
|
|
1860
|
+
/**
|
|
1861
|
+
*
|
|
1862
|
+
*/
|
|
1863
|
+
class JSONPath {
|
|
1864
|
+
/**
|
|
1865
|
+
*
|
|
1866
|
+
* @param environment -
|
|
1867
|
+
* @param selectors -
|
|
1868
|
+
*/
|
|
1869
|
+
constructor(environment, selectors) {
|
|
1870
|
+
this.environment = environment;
|
|
1871
|
+
this.selectors = selectors;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
/**
|
|
1875
|
+
*
|
|
1876
|
+
* @param value -
|
|
1877
|
+
* @returns
|
|
1878
|
+
*/
|
|
1879
|
+
query(value) {
|
|
1880
|
+
let nodes = new JSONPathNodeList([new JSONPathNode(value, [], value)]);
|
|
1881
|
+
for (const selector of this.selectors) {
|
|
1882
|
+
nodes = selector.resolve(nodes);
|
|
1883
|
+
}
|
|
1884
|
+
return nodes;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
/**
|
|
1888
|
+
*
|
|
1889
|
+
*/
|
|
1890
|
+
toString() {
|
|
1891
|
+
return `$${this.selectors.map(s => s.toString()).join("")}`;
|
|
1892
|
+
}
|
|
1893
|
+
singularQuery() {
|
|
1894
|
+
for (const selector of this.selectors) {
|
|
1895
|
+
if (selector instanceof NameSelector) continue;
|
|
1896
|
+
if (selector instanceof BracketedSelection && selector.items.length === 1 && (selector.items[0] instanceof NameSelector || selector.items[0] instanceof IndexSelector)) continue;
|
|
1897
|
+
return false;
|
|
1898
|
+
}
|
|
1899
|
+
return true;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
const PRECEDENCE_LOWEST = 1;
|
|
1904
|
+
const PRECEDENCE_LOGICALRIGHT = 3;
|
|
1905
|
+
const PRECEDENCE_LOGICAL_AND = 4;
|
|
1906
|
+
const PRECEDENCE_LOGICAL_OR = 5;
|
|
1907
|
+
const PRECEDENCE_COMPARISON = 6;
|
|
1908
|
+
const PRECEDENCES = new Map([[TokenKind.AND, PRECEDENCE_LOGICAL_AND], [TokenKind.EQ, PRECEDENCE_COMPARISON], [TokenKind.GE, PRECEDENCE_COMPARISON], [TokenKind.GT, PRECEDENCE_COMPARISON], [TokenKind.LE, PRECEDENCE_COMPARISON], [TokenKind.LT, PRECEDENCE_COMPARISON], [TokenKind.NE, PRECEDENCE_COMPARISON], [TokenKind.NOT, PRECEDENCE_LOGICALRIGHT], [TokenKind.OR, PRECEDENCE_LOGICAL_OR], [TokenKind.RPAREN, PRECEDENCE_LOWEST]]);
|
|
1909
|
+
const BINARY_OPERATORS = new Map([[TokenKind.AND, "&&"], [TokenKind.EQ, "=="], [TokenKind.GE, ">="], [TokenKind.GT, ">"], [TokenKind.LE, "<="], [TokenKind.LT, "<"], [TokenKind.NE, "!="], [TokenKind.OR, "||"]]);
|
|
1910
|
+
const COMPARISON_OPERATORS = new Set(["==", ">=", ">", "<=", "<", "!="]);
|
|
1911
|
+
class Parser {
|
|
1912
|
+
constructor(environment) {
|
|
1913
|
+
this.environment = environment;
|
|
1914
|
+
this.tokenMap = new Map([[TokenKind.FALSE, this.parseBoolean], [TokenKind.NUMBER, this.parseNumber], [TokenKind.LPAREN, this.parseGroupedExpression], [TokenKind.NOT, this.parsePrefixExpression], [TokenKind.NULL, this.parseNull], [TokenKind.ROOT, this.parseRootQuery], [TokenKind.CURRENT, this.parseRelativeQuery], [TokenKind.SINGLE_QUOTE_STRING, this.parseString], [TokenKind.DOUBLE_QUOTE_STRING, this.parseString], [TokenKind.TRUE, this.parseBoolean], [TokenKind.FUNCTION, this.parseFunction]]);
|
|
1915
|
+
}
|
|
1916
|
+
parse(stream) {
|
|
1917
|
+
if (stream.current.kind === TokenKind.ROOT) stream.next();
|
|
1918
|
+
const selectors = this.parsePath(stream);
|
|
1919
|
+
if (stream.current.kind !== TokenKind.EOF) {
|
|
1920
|
+
throw new JSONPathSyntaxError(`unexpected token '${stream.current.kind}'`, stream.current);
|
|
1921
|
+
}
|
|
1922
|
+
return selectors;
|
|
1923
|
+
}
|
|
1924
|
+
parsePath(stream) {
|
|
1925
|
+
let inFilter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
1926
|
+
const selectors = [];
|
|
1927
|
+
loop: for (;;) {
|
|
1928
|
+
switch (stream.current.kind) {
|
|
1929
|
+
case TokenKind.NAME:
|
|
1930
|
+
selectors.push(new NameSelector(this.environment, stream.current, stream.current.value, true));
|
|
1931
|
+
break;
|
|
1932
|
+
case TokenKind.WILD:
|
|
1933
|
+
selectors.push(new WildcardSelector(this.environment, stream.current, true));
|
|
1934
|
+
break;
|
|
1935
|
+
case TokenKind.DDOT:
|
|
1936
|
+
selectors.push(new RecursiveDescentSegment(this.environment, stream.current));
|
|
1937
|
+
break;
|
|
1938
|
+
case TokenKind.LBRACKET:
|
|
1939
|
+
selectors.push(this.parseBracketedSelection(stream));
|
|
1940
|
+
break;
|
|
1941
|
+
default:
|
|
1942
|
+
if (inFilter) {
|
|
1943
|
+
stream.backup();
|
|
1944
|
+
}
|
|
1945
|
+
break loop;
|
|
1946
|
+
}
|
|
1947
|
+
stream.next();
|
|
1948
|
+
}
|
|
1949
|
+
return selectors;
|
|
1950
|
+
}
|
|
1951
|
+
parseIndex(stream) {
|
|
1952
|
+
if (stream.current.value.length > 1 && stream.current.value.startsWith("0") || stream.current.value.startsWith("-0")) {
|
|
1953
|
+
throw new JSONPathSyntaxError("leading zero in index selector", stream.current);
|
|
1954
|
+
}
|
|
1955
|
+
return new IndexSelector(this.environment, stream.current, Number(stream.current.value));
|
|
1956
|
+
}
|
|
1957
|
+
parseSlice(stream) {
|
|
1958
|
+
const tok = stream.current;
|
|
1959
|
+
const indices = [];
|
|
1960
|
+
function maybeIndex(token) {
|
|
1961
|
+
if (token.kind === TokenKind.INDEX) {
|
|
1962
|
+
if (token.value.length > 1 && token.value.startsWith("0") || token.value.startsWith("-0")) {
|
|
1963
|
+
throw new JSONPathSyntaxError("leading zero in index selector", token);
|
|
1964
|
+
}
|
|
1965
|
+
return true;
|
|
1966
|
+
}
|
|
1967
|
+
return false;
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
// 1: or :
|
|
1971
|
+
if (maybeIndex(stream.current)) {
|
|
1972
|
+
indices.push(Number(stream.current.value));
|
|
1973
|
+
stream.next();
|
|
1974
|
+
stream.expect(TokenKind.COLON);
|
|
1975
|
+
stream.next();
|
|
1976
|
+
} else {
|
|
1977
|
+
indices.push(undefined);
|
|
1978
|
+
stream.expect(TokenKind.COLON);
|
|
1979
|
+
stream.next();
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
// 1 or 1: or : or ?
|
|
1983
|
+
if (maybeIndex(stream.current)) {
|
|
1984
|
+
indices.push(Number(stream.current.value));
|
|
1985
|
+
stream.next();
|
|
1986
|
+
if (stream.current.kind === TokenKind.COLON) {
|
|
1987
|
+
stream.next();
|
|
1988
|
+
}
|
|
1989
|
+
} else if (stream.current.kind === TokenKind.COLON) {
|
|
1990
|
+
indices.push(undefined);
|
|
1991
|
+
stream.expect(TokenKind.COLON);
|
|
1992
|
+
stream.next();
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
// 1 or ?
|
|
1996
|
+
if (maybeIndex(stream.current)) {
|
|
1997
|
+
indices.push(Number(stream.current.value));
|
|
1998
|
+
stream.next();
|
|
1999
|
+
}
|
|
2000
|
+
stream.backup();
|
|
2001
|
+
return new SliceSelector(this.environment, tok, ...indices);
|
|
2002
|
+
}
|
|
2003
|
+
parseBracketedSelection(stream) {
|
|
2004
|
+
const token = stream.next();
|
|
2005
|
+
const items = [];
|
|
2006
|
+
while (stream.current.kind !== TokenKind.RBRACKET) {
|
|
2007
|
+
switch (stream.current.kind) {
|
|
2008
|
+
case TokenKind.SINGLE_QUOTE_STRING:
|
|
2009
|
+
case TokenKind.DOUBLE_QUOTE_STRING:
|
|
2010
|
+
items.push(new NameSelector(this.environment, stream.current, this.decodeString(stream.current, true), false));
|
|
2011
|
+
break;
|
|
2012
|
+
case TokenKind.FILTER:
|
|
2013
|
+
items.push(this.parseFilter(stream));
|
|
2014
|
+
break;
|
|
2015
|
+
case TokenKind.INDEX:
|
|
2016
|
+
if (stream.peek.kind === TokenKind.COLON) {
|
|
2017
|
+
items.push(this.parseSlice(stream));
|
|
2018
|
+
} else {
|
|
2019
|
+
items.push(this.parseIndex(stream));
|
|
2020
|
+
}
|
|
2021
|
+
break;
|
|
2022
|
+
case TokenKind.COLON:
|
|
2023
|
+
items.push(this.parseSlice(stream));
|
|
2024
|
+
break;
|
|
2025
|
+
case TokenKind.WILD:
|
|
2026
|
+
items.push(new WildcardSelector(this.environment, stream.current));
|
|
2027
|
+
break;
|
|
2028
|
+
case TokenKind.EOF:
|
|
2029
|
+
throw new JSONPathSyntaxError("unexpected end of query", stream.current);
|
|
2030
|
+
default:
|
|
2031
|
+
throw new JSONPathSyntaxError(`unexpected token in bracketed selection '${stream.current.kind}'`, stream.current);
|
|
2032
|
+
}
|
|
2033
|
+
if (stream.peek.kind !== TokenKind.RBRACKET) {
|
|
2034
|
+
stream.expectPeek(TokenKind.COMMA);
|
|
2035
|
+
stream.next();
|
|
2036
|
+
}
|
|
2037
|
+
stream.next();
|
|
2038
|
+
}
|
|
2039
|
+
if (!items.length) {
|
|
2040
|
+
throw new JSONPathSyntaxError("empty bracketed segment", token);
|
|
2041
|
+
}
|
|
2042
|
+
return new BracketedSelection(this.environment, token, items);
|
|
2043
|
+
}
|
|
2044
|
+
parseFilter(stream) {
|
|
2045
|
+
const tok = stream.next();
|
|
2046
|
+
const expr = this.parseFilterExpression(stream);
|
|
2047
|
+
if (expr instanceof FunctionExtension) {
|
|
2048
|
+
const func = this.environment.filterRegister.get(expr.name);
|
|
2049
|
+
if (func && func.returnType === FunctionExpressionType.ValueType) {
|
|
2050
|
+
throw new JSONPathTypeError(`result of ${expr.name}() must be compared`, expr.token);
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
return new FilterSelector(this.environment, tok, new LogicalExpression(tok, expr));
|
|
2054
|
+
}
|
|
2055
|
+
parseBoolean(stream) {
|
|
2056
|
+
if (stream.current.kind === TokenKind.FALSE) return new BooleanLiteral(stream.current, false);
|
|
2057
|
+
return new BooleanLiteral(stream.current, true);
|
|
2058
|
+
}
|
|
2059
|
+
parseNull(stream) {
|
|
2060
|
+
return new NullLiteral(stream.current);
|
|
2061
|
+
}
|
|
2062
|
+
parseString(stream) {
|
|
2063
|
+
return new StringLiteral(stream.current, this.decodeString(stream.current));
|
|
2064
|
+
}
|
|
2065
|
+
parseNumber(stream) {
|
|
2066
|
+
return new NumberLiteral(stream.current, Number(stream.current.value));
|
|
2067
|
+
}
|
|
2068
|
+
parsePrefixExpression(stream) {
|
|
2069
|
+
stream.expect(TokenKind.NOT);
|
|
2070
|
+
stream.next();
|
|
2071
|
+
return new PrefixExpression(stream.current, "!", this.parseFilterExpression(stream, PRECEDENCE_LOGICALRIGHT));
|
|
2072
|
+
}
|
|
2073
|
+
parseInfixExpression(stream, left) {
|
|
2074
|
+
const tok = stream.next();
|
|
2075
|
+
const precedence = PRECEDENCES.get(tok.kind) || PRECEDENCE_LOWEST;
|
|
2076
|
+
const right = this.parseFilterExpression(stream, precedence);
|
|
2077
|
+
const operator = BINARY_OPERATORS.get(tok.kind);
|
|
2078
|
+
if (!operator) {
|
|
2079
|
+
throw new JSONPathSyntaxError(`unknown operator '${tok.kind}'`, tok);
|
|
2080
|
+
}
|
|
2081
|
+
this.throwForNonSingularQuery(left);
|
|
2082
|
+
this.throwForNonSingularQuery(right);
|
|
2083
|
+
if (COMPARISON_OPERATORS.has(operator)) {
|
|
2084
|
+
this.throwForNonComparableFunction(left);
|
|
2085
|
+
this.throwForNonComparableFunction(right);
|
|
2086
|
+
}
|
|
2087
|
+
return new InfixExpression(tok, left, operator, right);
|
|
2088
|
+
}
|
|
2089
|
+
parseGroupedExpression(stream) {
|
|
2090
|
+
stream.next();
|
|
2091
|
+
let expr = this.parseFilterExpression(stream);
|
|
2092
|
+
stream.next();
|
|
2093
|
+
while (stream.current.kind !== TokenKind.RPAREN) {
|
|
2094
|
+
if (stream.current.kind === TokenKind.EOF) {
|
|
2095
|
+
throw new JSONPathSyntaxError("unbalanced parentheses", stream.current);
|
|
2096
|
+
}
|
|
2097
|
+
expr = this.parseInfixExpression(stream, expr);
|
|
2098
|
+
}
|
|
2099
|
+
stream.expect(TokenKind.RPAREN);
|
|
2100
|
+
return expr;
|
|
2101
|
+
}
|
|
2102
|
+
parseRootQuery(stream) {
|
|
2103
|
+
const tok = stream.next();
|
|
2104
|
+
return new RootQuery(tok, new JSONPath(this.environment, this.parsePath(stream, true)));
|
|
2105
|
+
}
|
|
2106
|
+
parseRelativeQuery(stream) {
|
|
2107
|
+
const tok = stream.next();
|
|
2108
|
+
return new RelativeQuery(tok, new JSONPath(this.environment, this.parsePath(stream, true)));
|
|
2109
|
+
}
|
|
2110
|
+
parseFunction(stream) {
|
|
2111
|
+
const args = [];
|
|
2112
|
+
const tok = stream.next();
|
|
2113
|
+
while (stream.current.kind !== TokenKind.RPAREN) {
|
|
2114
|
+
const func = this.tokenMap.get(stream.current.kind);
|
|
2115
|
+
if (!func) {
|
|
2116
|
+
throw new JSONPathSyntaxError(`unexpected '${stream.current.value}'`, stream.current);
|
|
2117
|
+
}
|
|
2118
|
+
args.push(func.bind(this)(stream));
|
|
2119
|
+
if (stream.peek.kind !== TokenKind.RPAREN) {
|
|
2120
|
+
if (stream.peek.kind === TokenKind.RBRACKET) break;
|
|
2121
|
+
stream.expectPeek(TokenKind.COMMA);
|
|
2122
|
+
stream.next();
|
|
2123
|
+
}
|
|
2124
|
+
stream.next();
|
|
2125
|
+
}
|
|
2126
|
+
return new FunctionExtension(tok, tok.value, this.environment.checkWellTypedness(tok, args));
|
|
2127
|
+
}
|
|
2128
|
+
parseFilterExpression(stream) {
|
|
2129
|
+
let precedence = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : PRECEDENCE_LOWEST;
|
|
2130
|
+
const func = this.tokenMap.get(stream.current.kind);
|
|
2131
|
+
if (!func) {
|
|
2132
|
+
let msg;
|
|
2133
|
+
switch (stream.current.kind) {
|
|
2134
|
+
case TokenKind.EOF:
|
|
2135
|
+
case TokenKind.RBRACKET:
|
|
2136
|
+
msg = "end of expression";
|
|
2137
|
+
break;
|
|
2138
|
+
default:
|
|
2139
|
+
msg = `'${stream.current.value}`;
|
|
2140
|
+
}
|
|
2141
|
+
throw new JSONPathSyntaxError(`unexpected ${msg}`, stream.current);
|
|
2142
|
+
}
|
|
2143
|
+
let left = func.bind(this)(stream);
|
|
2144
|
+
for (;;) {
|
|
2145
|
+
const peekKind = stream.peek.kind;
|
|
2146
|
+
if (peekKind === TokenKind.EOF || peekKind === TokenKind.RBRACKET || (PRECEDENCES.get(peekKind) || PRECEDENCE_LOWEST) < precedence) {
|
|
2147
|
+
break;
|
|
2148
|
+
}
|
|
2149
|
+
if (!BINARY_OPERATORS.has(peekKind)) return left;
|
|
2150
|
+
stream.next();
|
|
2151
|
+
left = this.parseInfixExpression(stream, left);
|
|
2152
|
+
}
|
|
2153
|
+
return left;
|
|
2154
|
+
}
|
|
2155
|
+
decodeString(token) {
|
|
2156
|
+
let isName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
2157
|
+
try {
|
|
2158
|
+
return JSON.parse(token.kind === TokenKind.SINGLE_QUOTE_STRING ? `"${token.value.replaceAll('"', '\\"').replaceAll("\\'", "'")}"` : `"${token.value}"`);
|
|
2159
|
+
} catch {
|
|
2160
|
+
throw new JSONPathSyntaxError(`invalid ${isName ? "name selector" : "string literal"} '${token.value}'`, token);
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
throwForNonSingularQuery(expr) {
|
|
2164
|
+
if ((expr instanceof RootQuery || expr instanceof RelativeQuery) && !expr.path.singularQuery()) {
|
|
2165
|
+
throw new JSONPathSyntaxError("non-singular query is not comparable", expr.token);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
throwForNonComparableFunction(expr) {
|
|
2169
|
+
if (!(expr instanceof FunctionExtension)) return;
|
|
2170
|
+
const func = this.environment.filterRegister.get(expr.name);
|
|
2171
|
+
if (func && func.returnType !== FunctionExpressionType.ValueType) {
|
|
2172
|
+
throw new JSONPathTypeError(`result of ${expr.name}() is not comparable`, expr.token);
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
/**
|
|
2178
|
+
*
|
|
2179
|
+
*/
|
|
2180
|
+
|
|
2181
|
+
const defaultOptions = {
|
|
2182
|
+
strict: true,
|
|
2183
|
+
maxIntIndex: Math.pow(2, 53) - 1,
|
|
2184
|
+
minIntIndex: -Math.pow(2, 53) - 1
|
|
2185
|
+
};
|
|
2186
|
+
|
|
2187
|
+
/**
|
|
2188
|
+
*
|
|
2189
|
+
*/
|
|
2190
|
+
class JSONPathEnvironment {
|
|
2191
|
+
/**
|
|
2192
|
+
*
|
|
2193
|
+
*/
|
|
2194
|
+
filterRegister = new Map();
|
|
2195
|
+
/**
|
|
2196
|
+
*
|
|
2197
|
+
* @param options -
|
|
2198
|
+
*/
|
|
2199
|
+
constructor() {
|
|
2200
|
+
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultOptions;
|
|
2201
|
+
this.options = options;
|
|
2202
|
+
this.parser = new Parser(this);
|
|
2203
|
+
this.setupFilterFunctions();
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
/**
|
|
2207
|
+
*
|
|
2208
|
+
* @param path -
|
|
2209
|
+
* @returns
|
|
2210
|
+
*/
|
|
2211
|
+
compile(path) {
|
|
2212
|
+
return new JSONPath(this, this.parser.parse(new TokenStream(tokenize(path))));
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
/**
|
|
2216
|
+
*
|
|
2217
|
+
* @param path -
|
|
2218
|
+
* @param value -
|
|
2219
|
+
* @returns
|
|
2220
|
+
*/
|
|
2221
|
+
query(path, value) {
|
|
2222
|
+
return this.compile(path).query(value);
|
|
2223
|
+
}
|
|
2224
|
+
setupFilterFunctions() {
|
|
2225
|
+
this.filterRegister.set("count", new Count());
|
|
2226
|
+
this.filterRegister.set("length", new Length());
|
|
2227
|
+
this.filterRegister.set("search", new Search());
|
|
2228
|
+
this.filterRegister.set("match", new Match());
|
|
2229
|
+
this.filterRegister.set("value", new Value());
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
/**
|
|
2233
|
+
*
|
|
2234
|
+
* @param token -
|
|
2235
|
+
* @param args -
|
|
2236
|
+
*/
|
|
2237
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
2238
|
+
checkWellTypedness(token, args) {
|
|
2239
|
+
const func = this.filterRegister.get(token.value);
|
|
2240
|
+
if (!func) {
|
|
2241
|
+
throw new UndefinedFilterFunctionError(`no such function '${token.value}'`, token);
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
// Correct number of arguments
|
|
2245
|
+
if (args.length !== func.argTypes.length) {
|
|
2246
|
+
throw new JSONPathTypeError(`${token.value}() takes ${func.argTypes.length} argument${func.argTypes.length === 1 ? "" : "s"}, ${args.length} given`, token);
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
// Argument types
|
|
2250
|
+
for (const [typ, arg, idx] of func.argTypes.map((t, i) => [t, args[i], i])) {
|
|
2251
|
+
switch (typ) {
|
|
2252
|
+
case FunctionExpressionType.ValueType:
|
|
2253
|
+
if (!(arg instanceof FilterExpressionLiteral || arg instanceof JSONPathQuery && arg.path.singularQuery())) {
|
|
2254
|
+
throw new JSONPathTypeError(`${token.value}() argument ${idx} must be of ValueType`, arg.token);
|
|
2255
|
+
}
|
|
2256
|
+
break;
|
|
2257
|
+
case FunctionExpressionType.LogicalType:
|
|
2258
|
+
if (!(arg instanceof BooleanLiteral)) {
|
|
2259
|
+
throw new JSONPathTypeError(`${token.value}() argument ${idx} must be of LogicalType`, arg.token);
|
|
2260
|
+
}
|
|
2261
|
+
break;
|
|
2262
|
+
case FunctionExpressionType.NodesType:
|
|
2263
|
+
if (!(arg instanceof JSONPathQuery)) {
|
|
2264
|
+
throw new JSONPathTypeError(`${token.value}() argument ${idx} must be of NodesType`, arg.token);
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
return args;
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
var index$2 = /*#__PURE__*/Object.freeze({
|
|
2273
|
+
__proto__: null,
|
|
2274
|
+
Count: Count,
|
|
2275
|
+
FunctionExpressionType: FunctionExpressionType,
|
|
2276
|
+
Length: Length,
|
|
2277
|
+
Match: Match,
|
|
2278
|
+
Search: Search,
|
|
2279
|
+
Value: Value
|
|
2280
|
+
});
|
|
2281
|
+
|
|
2282
|
+
const DEFAULT_ENVIRONMENT = new JSONPathEnvironment();
|
|
2283
|
+
|
|
2284
|
+
/**
|
|
2285
|
+
* Query JSON value _value_ with JSONPath expression _path_.
|
|
2286
|
+
* @param path - A JSONPath expression/query.
|
|
2287
|
+
* @param value - The JSON-like value the JSONPath query is applied to.
|
|
2288
|
+
* @returns A list of JSONPathNode objects, one for each value matched
|
|
2289
|
+
* by _path_ in _value_.
|
|
2290
|
+
*
|
|
2291
|
+
* @throws {@link JSONPathSyntaxError}
|
|
2292
|
+
* If the path does not conform to standard syntax.
|
|
2293
|
+
*
|
|
2294
|
+
* @throws {@link JSONPathTypeError}
|
|
2295
|
+
* If filter function arguments are invalid, or filter expression are
|
|
2296
|
+
* used in an invalid way.
|
|
2297
|
+
*/
|
|
2298
|
+
function query(path, value) {
|
|
2299
|
+
return DEFAULT_ENVIRONMENT.query(path, value);
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
/**
|
|
2303
|
+
* Compile JSONPath _path_ for later use.
|
|
2304
|
+
* @param path - A JSONPath expression/query.
|
|
2305
|
+
* @returns A path object with a `query()` method.
|
|
2306
|
+
*
|
|
2307
|
+
* @throws {@link JSONPathSyntaxError}
|
|
2308
|
+
* If the path does not conform to standard syntax.
|
|
2309
|
+
*
|
|
2310
|
+
* @throws {@link JSONPathTypeError}
|
|
2311
|
+
* If filter function arguments are invalid, or filter expression are
|
|
2312
|
+
* used in an invalid way.
|
|
2313
|
+
*/
|
|
2314
|
+
function compile(path) {
|
|
2315
|
+
return DEFAULT_ENVIRONMENT.compile(path);
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
var index$1 = /*#__PURE__*/Object.freeze({
|
|
2319
|
+
__proto__: null,
|
|
2320
|
+
DEFAULT_ENVIRONMENT: DEFAULT_ENVIRONMENT,
|
|
2321
|
+
FunctionExpressionType: FunctionExpressionType,
|
|
2322
|
+
JSONPath: JSONPath,
|
|
2323
|
+
JSONPathEnvironment: JSONPathEnvironment,
|
|
2324
|
+
JSONPathError: JSONPathError,
|
|
2325
|
+
JSONPathIndexError: JSONPathIndexError,
|
|
2326
|
+
JSONPathLexerError: JSONPathLexerError,
|
|
2327
|
+
JSONPathNode: JSONPathNode,
|
|
2328
|
+
JSONPathNodeList: JSONPathNodeList,
|
|
2329
|
+
JSONPathSyntaxError: JSONPathSyntaxError,
|
|
2330
|
+
JSONPathTypeError: JSONPathTypeError,
|
|
2331
|
+
Nothing: Nothing,
|
|
2332
|
+
Token: Token,
|
|
2333
|
+
TokenKind: TokenKind,
|
|
2334
|
+
compile: compile,
|
|
2335
|
+
expressions: expression,
|
|
2336
|
+
functions: index$2,
|
|
2337
|
+
query: query,
|
|
2338
|
+
selectors: selectors
|
|
2339
|
+
});
|
|
2340
|
+
|
|
2341
|
+
/**
|
|
2342
|
+
* Base class for all JSON Patch errors.
|
|
2343
|
+
*/
|
|
2344
|
+
class JSONPatchError extends Error {
|
|
2345
|
+
constructor(message) {
|
|
2346
|
+
super(message);
|
|
2347
|
+
this.message = message;
|
|
2348
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
2349
|
+
this.name = "JSONPatchError";
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
class JSONPatchTestFailure extends JSONPatchError {
|
|
2353
|
+
constructor(message) {
|
|
2354
|
+
super(message);
|
|
2355
|
+
this.message = message;
|
|
2356
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
2357
|
+
this.name = "JSONPatchTestFailure";
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
/**
|
|
2362
|
+
* A JSON Patch operation.
|
|
2363
|
+
*/
|
|
2364
|
+
|
|
2365
|
+
/**
|
|
2366
|
+
* The JSON Patch _add_ operation.
|
|
2367
|
+
*/
|
|
2368
|
+
class OpAdd {
|
|
2369
|
+
name = "add";
|
|
2370
|
+
constructor(path, value) {
|
|
2371
|
+
this.path = path;
|
|
2372
|
+
this.value = value;
|
|
2373
|
+
}
|
|
2374
|
+
apply(value, index) {
|
|
2375
|
+
const [parent, obj] = this.path.resolveWithParent(value);
|
|
2376
|
+
if (parent === UNDEFINED) {
|
|
2377
|
+
// Replace the root object.
|
|
2378
|
+
return this.value;
|
|
2379
|
+
}
|
|
2380
|
+
const target = this.path.tokens.at(-1);
|
|
2381
|
+
if (target === undefined) {
|
|
2382
|
+
// this should not be possible
|
|
2383
|
+
throw new JSONPatchError(`unexpected operation on 'undefined' (${this.name}:${index})`);
|
|
2384
|
+
} else if (isArray(parent)) {
|
|
2385
|
+
if (obj === UNDEFINED) {
|
|
2386
|
+
if (target === "-") {
|
|
2387
|
+
parent.push(this.value);
|
|
2388
|
+
} else {
|
|
2389
|
+
throw new JSONPatchError(`index out of range (${this.name}:${index})`);
|
|
2390
|
+
}
|
|
2391
|
+
} else {
|
|
2392
|
+
parent.splice(Number(target), 0, this.value);
|
|
2393
|
+
}
|
|
2394
|
+
} else if (isObject(parent)) {
|
|
2395
|
+
parent[target] = this.value;
|
|
2396
|
+
} else {
|
|
2397
|
+
throw new JSONPatchError(`unexpected operation on '${typeof parent}' (${this.name}:${index})`);
|
|
2398
|
+
}
|
|
2399
|
+
return value;
|
|
2400
|
+
}
|
|
2401
|
+
toObject() {
|
|
2402
|
+
return {
|
|
2403
|
+
op: this.name,
|
|
2404
|
+
path: this.path.toString(),
|
|
2405
|
+
value: this.value
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
/**
|
|
2411
|
+
* The JSON Patch _remove_ operation.
|
|
2412
|
+
*/
|
|
2413
|
+
class OpRemove {
|
|
2414
|
+
name = "remove";
|
|
2415
|
+
constructor(path) {
|
|
2416
|
+
this.path = path;
|
|
2417
|
+
}
|
|
2418
|
+
apply(value, index) {
|
|
2419
|
+
const [parent, obj] = this.path.resolveWithParent(value);
|
|
2420
|
+
if (parent === UNDEFINED) {
|
|
2421
|
+
throw new JSONPatchError(`can't remove root (${this.name}:${index})`);
|
|
2422
|
+
}
|
|
2423
|
+
const target = this.path.tokens.at(-1);
|
|
2424
|
+
if (target === undefined) {
|
|
2425
|
+
// this should not be possible
|
|
2426
|
+
throw new JSONPatchError(`unexpected operation on 'undefined' (${this.name}:${index})`);
|
|
2427
|
+
} else if (isArray(parent)) {
|
|
2428
|
+
if (obj === UNDEFINED) {
|
|
2429
|
+
throw new JSONPatchError(`can't remove nonexistent item (${this.name}:${index})`);
|
|
2430
|
+
}
|
|
2431
|
+
parent.splice(Number(target), 1);
|
|
2432
|
+
} else if (isObject(parent)) {
|
|
2433
|
+
if (obj === UNDEFINED) {
|
|
2434
|
+
throw new JSONPatchError(`can't remove nonexistent property (${this.name}:${index})`);
|
|
2435
|
+
}
|
|
2436
|
+
delete parent[target];
|
|
2437
|
+
} else {
|
|
2438
|
+
throw new JSONPatchError(`unexpected operation on '${typeof parent}' (${this.name}:${index})`);
|
|
2439
|
+
}
|
|
2440
|
+
return value;
|
|
2441
|
+
}
|
|
2442
|
+
toObject() {
|
|
2443
|
+
return {
|
|
2444
|
+
op: this.name,
|
|
2445
|
+
path: this.path.toString()
|
|
2446
|
+
};
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
/**
|
|
2451
|
+
* The JSON Patch _replace_ operation.
|
|
2452
|
+
*/
|
|
2453
|
+
class OpReplace {
|
|
2454
|
+
name = "replace";
|
|
2455
|
+
constructor(path, value) {
|
|
2456
|
+
this.path = path;
|
|
2457
|
+
this.value = value;
|
|
2458
|
+
}
|
|
2459
|
+
apply(value, index) {
|
|
2460
|
+
const [parent, obj] = this.path.resolveWithParent(value);
|
|
2461
|
+
if (parent === UNDEFINED) {
|
|
2462
|
+
// Replace the root object.
|
|
2463
|
+
return this.value;
|
|
2464
|
+
}
|
|
2465
|
+
const target = this.path.tokens.at(-1);
|
|
2466
|
+
if (target === undefined) {
|
|
2467
|
+
// this should not be possible
|
|
2468
|
+
throw new JSONPatchError(`unexpected operation on 'undefined' (${this.name}:${index})`);
|
|
2469
|
+
}
|
|
2470
|
+
if (isArray(parent)) {
|
|
2471
|
+
if (obj === UNDEFINED) {
|
|
2472
|
+
throw new JSONPatchError(`can't replace nonexistent item (${this.name}:${index})`);
|
|
2473
|
+
}
|
|
2474
|
+
parent.splice(Number(target), 1, this.value);
|
|
2475
|
+
} else if (isObject(parent)) {
|
|
2476
|
+
if (obj === UNDEFINED) {
|
|
2477
|
+
throw new JSONPatchError(`can't replace nonexistent property (${this.name}:${index})`);
|
|
2478
|
+
}
|
|
2479
|
+
parent[target] = this.value;
|
|
2480
|
+
} else {
|
|
2481
|
+
throw new JSONPatchError(`unexpected operation on '${typeof parent}' (${this.name}:${index})`);
|
|
2482
|
+
}
|
|
2483
|
+
return value;
|
|
2484
|
+
}
|
|
2485
|
+
toObject() {
|
|
2486
|
+
return {
|
|
2487
|
+
op: this.name,
|
|
2488
|
+
path: this.path.toString(),
|
|
2489
|
+
value: this.value
|
|
2490
|
+
};
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
/**
|
|
2495
|
+
* The JSON Patch _move_ operation.
|
|
2496
|
+
*/
|
|
2497
|
+
class OpMove {
|
|
2498
|
+
name = "move";
|
|
2499
|
+
constructor(from, path) {
|
|
2500
|
+
this.from = from;
|
|
2501
|
+
this.path = path;
|
|
2502
|
+
}
|
|
2503
|
+
apply(value, index) {
|
|
2504
|
+
if (this.path.isRelativeTo(this.from)) {
|
|
2505
|
+
throw new JSONPatchError(`can't move object to one of its own children (${this.name}:${index})`);
|
|
2506
|
+
}
|
|
2507
|
+
const [sourceParent, sourceObj] = this.from.resolveWithParent(value);
|
|
2508
|
+
if (sourceObj === UNDEFINED) {
|
|
2509
|
+
throw new JSONPatchError(`source object does not exist (${this.name}:${index})`);
|
|
2510
|
+
}
|
|
2511
|
+
const sourceTarget = this.from.tokens.at(-1);
|
|
2512
|
+
if (sourceTarget === undefined) {
|
|
2513
|
+
// this should not be possible
|
|
2514
|
+
throw new JSONPatchError(`unexpected operation on 'undefined' (${this.name}:${index})`);
|
|
2515
|
+
}
|
|
2516
|
+
if (isArray(sourceParent)) {
|
|
2517
|
+
sourceParent.splice(Number(sourceTarget), 1);
|
|
2518
|
+
} else if (isObject(sourceParent)) {
|
|
2519
|
+
delete sourceParent[sourceTarget];
|
|
2520
|
+
}
|
|
2521
|
+
const [destParent, _] = this.path.resolveWithParent(value);
|
|
2522
|
+
if (destParent === UNDEFINED) {
|
|
2523
|
+
// move source to root
|
|
2524
|
+
return sourceObj;
|
|
2525
|
+
}
|
|
2526
|
+
const destTarget = this.path.tokens.at(-1);
|
|
2527
|
+
if (destTarget === undefined) {
|
|
2528
|
+
// this should not be possible
|
|
2529
|
+
throw new JSONPatchError(`unexpected operation on 'undefined' (${this.name}:${index})`);
|
|
2530
|
+
}
|
|
2531
|
+
if (isArray(destParent)) {
|
|
2532
|
+
destParent.splice(Number(destTarget), 0, sourceObj);
|
|
2533
|
+
} else if (isObject(destParent)) {
|
|
2534
|
+
destParent[destTarget] = sourceObj;
|
|
2535
|
+
} else {
|
|
2536
|
+
throw new JSONPatchError(`unexpected operation on '${typeof parent}' (${this.name}:${index})`);
|
|
2537
|
+
}
|
|
2538
|
+
return value;
|
|
2539
|
+
}
|
|
2540
|
+
toObject() {
|
|
2541
|
+
return {
|
|
2542
|
+
op: this.name,
|
|
2543
|
+
from: this.from.toString(),
|
|
2544
|
+
path: this.path.toString()
|
|
2545
|
+
};
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
/**
|
|
2550
|
+
* The JSON Patch _copy_ operation.
|
|
2551
|
+
*/
|
|
2552
|
+
class OpCopy {
|
|
2553
|
+
name = "copy";
|
|
2554
|
+
constructor(from, path) {
|
|
2555
|
+
this.from = from;
|
|
2556
|
+
this.path = path;
|
|
2557
|
+
}
|
|
2558
|
+
apply(value, index) {
|
|
2559
|
+
const [_, sourceObj] = this.from.resolveWithParent(value);
|
|
2560
|
+
if (sourceObj === UNDEFINED) {
|
|
2561
|
+
throw new JSONPatchError(`source object does not exist (${this.name}:${index})`);
|
|
2562
|
+
}
|
|
2563
|
+
const [destParent] = this.path.resolveWithParent(value);
|
|
2564
|
+
if (destParent === UNDEFINED) {
|
|
2565
|
+
// copy source to root
|
|
2566
|
+
return this.deepCopy(sourceObj);
|
|
2567
|
+
}
|
|
2568
|
+
const destTarget = this.path.tokens.at(-1);
|
|
2569
|
+
if (destTarget === undefined) {
|
|
2570
|
+
// this should not be possible
|
|
2571
|
+
throw new JSONPatchError(`unexpected operation on 'undefined' (${this.name}:${index})`);
|
|
2572
|
+
}
|
|
2573
|
+
if (isArray(destParent)) {
|
|
2574
|
+
destParent.splice(Number(destTarget), 0, this.deepCopy(sourceObj));
|
|
2575
|
+
} else if (isObject(destParent)) {
|
|
2576
|
+
destParent[destTarget] = this.deepCopy(sourceObj);
|
|
2577
|
+
} else {
|
|
2578
|
+
throw new JSONPatchError(`unexpected operation on '${typeof parent}' (${this.name}:${index})`);
|
|
2579
|
+
}
|
|
2580
|
+
return value;
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
// eslint-disable-next-line sonarjs/no-identical-functions
|
|
2584
|
+
toObject() {
|
|
2585
|
+
return {
|
|
2586
|
+
op: this.name,
|
|
2587
|
+
from: this.from.toString(),
|
|
2588
|
+
path: this.path.toString()
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2591
|
+
deepCopy(value) {
|
|
2592
|
+
return JSON.parse(JSON.stringify(value));
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
/**
|
|
2597
|
+
* The JSON Patch _test_ operation.
|
|
2598
|
+
*/
|
|
2599
|
+
class OpTest {
|
|
2600
|
+
name = "test";
|
|
2601
|
+
constructor(path, value) {
|
|
2602
|
+
this.path = path;
|
|
2603
|
+
this.value = value;
|
|
2604
|
+
}
|
|
2605
|
+
apply(value, index) {
|
|
2606
|
+
const [_, obj] = this.path.resolveWithParent(value);
|
|
2607
|
+
if (!deepEquals(obj, this.value)) {
|
|
2608
|
+
throw new JSONPatchTestFailure(`test failed (${this.name}:${index})`);
|
|
2609
|
+
}
|
|
2610
|
+
return value;
|
|
2611
|
+
}
|
|
2612
|
+
toObject() {
|
|
2613
|
+
return {
|
|
2614
|
+
op: this.name,
|
|
2615
|
+
path: this.path.toString(),
|
|
2616
|
+
value: this.value
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
/**
|
|
2622
|
+
*
|
|
2623
|
+
*/
|
|
2624
|
+
class JSONPatch {
|
|
2625
|
+
ops = [];
|
|
2626
|
+
|
|
2627
|
+
/**
|
|
2628
|
+
*
|
|
2629
|
+
* @param ops -
|
|
2630
|
+
*/
|
|
2631
|
+
constructor(ops) {
|
|
2632
|
+
if (ops) {
|
|
2633
|
+
this.build(ops);
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
/**
|
|
2638
|
+
*
|
|
2639
|
+
* @param path -
|
|
2640
|
+
* @param value -
|
|
2641
|
+
* @returns
|
|
2642
|
+
*/
|
|
2643
|
+
add(path, value) {
|
|
2644
|
+
this.ops.push(new OpAdd(this.ensurePointer(path, "add", this.ops.length), value));
|
|
2645
|
+
return this;
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
/**
|
|
2649
|
+
*
|
|
2650
|
+
* @param path -
|
|
2651
|
+
*/
|
|
2652
|
+
remove(path) {
|
|
2653
|
+
this.ops.push(new OpRemove(this.ensurePointer(path, "remove", this.ops.length)));
|
|
2654
|
+
return this;
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
/**
|
|
2658
|
+
*
|
|
2659
|
+
* @param path -
|
|
2660
|
+
* @param value -
|
|
2661
|
+
* @returns
|
|
2662
|
+
*/
|
|
2663
|
+
replace(path, value) {
|
|
2664
|
+
this.ops.push(new OpReplace(this.ensurePointer(path, "replace", this.ops.length), value));
|
|
2665
|
+
return this;
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
/**
|
|
2669
|
+
*
|
|
2670
|
+
* @param from -
|
|
2671
|
+
* @param path -
|
|
2672
|
+
* @returns
|
|
2673
|
+
*/
|
|
2674
|
+
move(from, path) {
|
|
2675
|
+
this.ops.push(new OpMove(this.ensurePointer(from, "move", this.ops.length), this.ensurePointer(path, "move", this.ops.length)));
|
|
2676
|
+
return this;
|
|
2677
|
+
}
|
|
2678
|
+
/**
|
|
2679
|
+
*
|
|
2680
|
+
* @param from -
|
|
2681
|
+
* @param path -
|
|
2682
|
+
* @returns
|
|
2683
|
+
*/
|
|
2684
|
+
copy(from, path) {
|
|
2685
|
+
this.ops.push(new OpCopy(this.ensurePointer(from, "copy", this.ops.length), this.ensurePointer(path, "copy", this.ops.length)));
|
|
2686
|
+
return this;
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
/**
|
|
2690
|
+
*
|
|
2691
|
+
* @param path -
|
|
2692
|
+
* @param value -
|
|
2693
|
+
* @returns
|
|
2694
|
+
*/
|
|
2695
|
+
test(path, value) {
|
|
2696
|
+
this.ops.push(new OpTest(this.ensurePointer(path, "test", this.ops.length), value));
|
|
2697
|
+
return this;
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
/**
|
|
2701
|
+
*
|
|
2702
|
+
* @param value -
|
|
2703
|
+
*/
|
|
2704
|
+
apply(value) {
|
|
2705
|
+
let _value = value;
|
|
2706
|
+
for (let i = 0; i < this.ops.length; i++) {
|
|
2707
|
+
const op = this.ops[i];
|
|
2708
|
+
try {
|
|
2709
|
+
_value = op.apply(_value, i);
|
|
2710
|
+
} catch (error) {
|
|
2711
|
+
if (error instanceof JSONPointerResolutionError) {
|
|
2712
|
+
throw new JSONPatchError(`${error.message} (${op.name}:${i})`);
|
|
2713
|
+
}
|
|
2714
|
+
throw error;
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
return _value;
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
/**
|
|
2721
|
+
*
|
|
2722
|
+
* @returns
|
|
2723
|
+
*/
|
|
2724
|
+
toArray() {
|
|
2725
|
+
return this.ops.map(op => op.toObject());
|
|
2726
|
+
}
|
|
2727
|
+
build(ops) {
|
|
2728
|
+
for (let i = 0; i < ops.length; i++) {
|
|
2729
|
+
const operation = ops[i];
|
|
2730
|
+
switch (operation.op) {
|
|
2731
|
+
case "add":
|
|
2732
|
+
this.add(this.opPointer(operation, "path", "add", i), this.opValue(operation, "value", "add", i));
|
|
2733
|
+
break;
|
|
2734
|
+
case "remove":
|
|
2735
|
+
this.remove(this.opPointer(operation, "path", "remove", i));
|
|
2736
|
+
break;
|
|
2737
|
+
case "replace":
|
|
2738
|
+
this.replace(this.opPointer(operation, "path", "replace", i), this.opValue(operation, "value", "replace", i));
|
|
2739
|
+
break;
|
|
2740
|
+
case "move":
|
|
2741
|
+
this.move(this.opPointer(operation, "from", "move", i), this.opPointer(operation, "path", "move", i));
|
|
2742
|
+
break;
|
|
2743
|
+
case "copy":
|
|
2744
|
+
this.copy(this.opPointer(operation, "from", "copy", i), this.opPointer(operation, "path", "copy", i));
|
|
2745
|
+
break;
|
|
2746
|
+
case "test":
|
|
2747
|
+
this.test(this.opPointer(operation, "path", "test", i), this.opValue(operation, "value", "test", i));
|
|
2748
|
+
break;
|
|
2749
|
+
default:
|
|
2750
|
+
throw new JSONPatchError(`expected 'op' to be one of 'add', 'remove', 'replace', 'move', 'copy' or 'test' (${operation.op}:${i})`);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
opPointer(opObj, key, op, index) {
|
|
2755
|
+
if (!Object.hasOwn(opObj, key)) {
|
|
2756
|
+
throw new JSONPatchError(`missing property '${key}' (${op}:${index})`);
|
|
2757
|
+
}
|
|
2758
|
+
const p = opObj[key];
|
|
2759
|
+
if (!isString(p)) {
|
|
2760
|
+
throw new JSONPatchError(`expected a JSON Pointer string for '${key}', found ${typeof p} (${op}:${index})`);
|
|
2761
|
+
}
|
|
2762
|
+
try {
|
|
2763
|
+
return new JSONPointer(p);
|
|
2764
|
+
} catch (error) {
|
|
2765
|
+
if (error instanceof JSONPointerError) {
|
|
2766
|
+
throw new JSONPatchError(`${error.message} (${op}:${index})`);
|
|
2767
|
+
}
|
|
2768
|
+
throw error;
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
opValue(opObj, key, op, index) {
|
|
2772
|
+
if (!Object.hasOwn(opObj, key)) {
|
|
2773
|
+
throw new JSONPatchError(`missing property '${key}' (${op}:${index})`);
|
|
2774
|
+
}
|
|
2775
|
+
return opObj[key];
|
|
2776
|
+
}
|
|
2777
|
+
ensurePointer(p, op, index) {
|
|
2778
|
+
if (p instanceof JSONPointer) {
|
|
2779
|
+
return p;
|
|
2780
|
+
}
|
|
2781
|
+
if (!isString(p)) {
|
|
2782
|
+
throw new JSONPatchError(`expected a JSON Pointer string, found ${typeof p} (${op}:${index})`);
|
|
2783
|
+
}
|
|
2784
|
+
try {
|
|
2785
|
+
return new JSONPointer(p);
|
|
2786
|
+
} catch (error) {
|
|
2787
|
+
if (error instanceof JSONPointerError) {
|
|
2788
|
+
throw new JSONPatchError(`${error.message} (${op}:${index})`);
|
|
2789
|
+
}
|
|
2790
|
+
throw error;
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2795
|
+
/**
|
|
2796
|
+
* Apply the JSON Patch _patch_ to JSON-like data _value_.
|
|
2797
|
+
* @param ops - JSON Patch operations following RFC 6902.
|
|
2798
|
+
* @param value - The target JSON-like document to patch.
|
|
2799
|
+
*/
|
|
2800
|
+
function apply(ops, value) {
|
|
2801
|
+
return new JSONPatch(ops).apply(value);
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
var index = /*#__PURE__*/Object.freeze({
|
|
2805
|
+
__proto__: null,
|
|
2806
|
+
JSONPatch: JSONPatch,
|
|
2807
|
+
JSONPatchError: JSONPatchError,
|
|
2808
|
+
JSONPatchTestFailure: JSONPatchTestFailure,
|
|
2809
|
+
apply: apply
|
|
2810
|
+
});
|
|
2811
|
+
|
|
2812
|
+
const version = "0.1.0";
|
|
2813
|
+
|
|
2814
|
+
exports.FunctionExpressionType = FunctionExpressionType;
|
|
2815
|
+
exports.JSONPatch = JSONPatch;
|
|
2816
|
+
exports.JSONPatchError = JSONPatchError;
|
|
2817
|
+
exports.JSONPatchTestFailure = JSONPatchTestFailure;
|
|
2818
|
+
exports.JSONPath = JSONPath;
|
|
2819
|
+
exports.JSONPathEnvironment = JSONPathEnvironment;
|
|
2820
|
+
exports.JSONPathError = JSONPathError;
|
|
2821
|
+
exports.JSONPathIndexError = JSONPathIndexError;
|
|
2822
|
+
exports.JSONPathLexerError = JSONPathLexerError;
|
|
2823
|
+
exports.JSONPathNode = JSONPathNode;
|
|
2824
|
+
exports.JSONPathNodeList = JSONPathNodeList;
|
|
2825
|
+
exports.JSONPathSyntaxError = JSONPathSyntaxError;
|
|
2826
|
+
exports.JSONPathTypeError = JSONPathTypeError;
|
|
2827
|
+
exports.JSONPointer = JSONPointer;
|
|
2828
|
+
exports.Nothing = Nothing;
|
|
2829
|
+
exports.Token = Token;
|
|
2830
|
+
exports.TokenKind = TokenKind;
|
|
2831
|
+
exports.UNDEFINED = UNDEFINED;
|
|
2832
|
+
exports.apply = apply;
|
|
2833
|
+
exports.compile = compile;
|
|
2834
|
+
exports.jsonpatch = index;
|
|
2835
|
+
exports.jsonpath = index$1;
|
|
2836
|
+
exports.jsonpointer = index$3;
|
|
2837
|
+
exports.query = query;
|
|
2838
|
+
exports.resolve = resolve;
|
|
2839
|
+
exports.version = version;
|
|
2840
|
+
|
|
2841
|
+
return exports;
|
|
2842
|
+
|
|
2843
|
+
})({});
|