json-p3 0.1.0 → 0.2.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/README.md +26 -211
- package/dist/index.d.ts +2 -2
- package/dist/json-p3.cjs.js +279 -45
- package/dist/json-p3.esm.js +277 -46
- package/dist/json-p3.iife.js +279 -45
- package/dist/json-p3.iife.min.js +1 -1
- package/dist/json-p3.iife.min.js.map +1 -1
- package/dist/patch/patch.d.ts +4 -0
- package/dist/path/environment.d.ts +56 -10
- package/dist/path/errors.d.ts +8 -0
- package/dist/path/expression.d.ts +1 -0
- package/dist/path/index.d.ts +12 -2
- package/dist/path/path.d.ts +10 -1
- package/dist/path/selectors.d.ts +0 -1
- package/dist/pointer/errors.d.ts +15 -0
- package/dist/pointer/index.d.ts +1 -1
- package/dist/pointer/pointer.d.ts +31 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +2 -2
package/dist/json-p3.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* json-p3 version 0.
|
|
2
|
+
* json-p3 version 0.2.0
|
|
3
3
|
* https://github.com/jg-rp/json-p3
|
|
4
4
|
*
|
|
5
5
|
* MIT License
|
|
@@ -124,6 +124,20 @@ class JSONPathSyntaxError extends JSONPathError {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Error thrown when the maximum recursion depth is reached.
|
|
129
|
+
*/
|
|
130
|
+
class JSONPathRecursionLimitError extends JSONPathError {
|
|
131
|
+
constructor(message, token) {
|
|
132
|
+
super(message, token);
|
|
133
|
+
this.message = message;
|
|
134
|
+
this.token = token;
|
|
135
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
136
|
+
this.name = "JSONPathRecursionLimitError";
|
|
137
|
+
this.message = withErrorContext(message, token);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
127
141
|
/**
|
|
128
142
|
* Common types and type predicates.
|
|
129
143
|
*/
|
|
@@ -232,6 +246,10 @@ class JSONPointerError extends Error {
|
|
|
232
246
|
this.name = "JSONPointerError";
|
|
233
247
|
}
|
|
234
248
|
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Base class for JSON Pointer resolution errors.
|
|
252
|
+
*/
|
|
235
253
|
class JSONPointerResolutionError extends JSONPointerError {
|
|
236
254
|
constructor(message) {
|
|
237
255
|
super(message);
|
|
@@ -240,6 +258,10 @@ class JSONPointerResolutionError extends JSONPointerError {
|
|
|
240
258
|
this.name = "JSONPointerResolutionError";
|
|
241
259
|
}
|
|
242
260
|
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Error thrown due to an out of range index when resolving a JSON Pointer.
|
|
264
|
+
*/
|
|
243
265
|
class JSONPointerIndexError extends JSONPointerResolutionError {
|
|
244
266
|
constructor(message) {
|
|
245
267
|
super(message);
|
|
@@ -248,6 +270,10 @@ class JSONPointerIndexError extends JSONPointerResolutionError {
|
|
|
248
270
|
this.name = "JSONPointerIndexError";
|
|
249
271
|
}
|
|
250
272
|
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Error thrown due to a missing property when resolving a JSON Pointer.
|
|
276
|
+
*/
|
|
251
277
|
class JSONPointerKeyError extends JSONPointerResolutionError {
|
|
252
278
|
constructor(message) {
|
|
253
279
|
super(message);
|
|
@@ -256,6 +282,10 @@ class JSONPointerKeyError extends JSONPointerResolutionError {
|
|
|
256
282
|
this.name = "JSONPointerKeyError";
|
|
257
283
|
}
|
|
258
284
|
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Error thrown due to invalid JSON Pointer syntax.
|
|
288
|
+
*/
|
|
259
289
|
class JSONPointerSyntaxError extends JSONPointerError {
|
|
260
290
|
constructor(message) {
|
|
261
291
|
super(message);
|
|
@@ -264,6 +294,10 @@ class JSONPointerSyntaxError extends JSONPointerError {
|
|
|
264
294
|
this.name = "JSONPointerSyntaxError";
|
|
265
295
|
}
|
|
266
296
|
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Error thrown when trying to resolve a property or index against a primitive value.
|
|
300
|
+
*/
|
|
267
301
|
class JSONPointerTypeError extends JSONPointerResolutionError {
|
|
268
302
|
constructor(message) {
|
|
269
303
|
super(message);
|
|
@@ -323,8 +357,6 @@ class JSONPointer {
|
|
|
323
357
|
}
|
|
324
358
|
}
|
|
325
359
|
|
|
326
|
-
// TODO: add `resolveWithFallback` to handle explicit `undefined`
|
|
327
|
-
|
|
328
360
|
/**
|
|
329
361
|
*
|
|
330
362
|
* @param value -
|
|
@@ -363,6 +395,8 @@ class JSONPointer {
|
|
|
363
395
|
}
|
|
364
396
|
return pointer.split("/").map(token => token.replaceAll("~1", "/").replaceAll("~0", "~")).slice(1);
|
|
365
397
|
}
|
|
398
|
+
|
|
399
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
366
400
|
getItem(val, token, idx) {
|
|
367
401
|
// NOTE:
|
|
368
402
|
// - string primitives "have own" indices and `length`.
|
|
@@ -372,12 +406,23 @@ class JSONPointer {
|
|
|
372
406
|
if (isArray(val)) {
|
|
373
407
|
if (token !== "length" && Object.hasOwn(val, token)) {
|
|
374
408
|
return val[Number(token)];
|
|
409
|
+
} else if (token.startsWith("#")) {
|
|
410
|
+
// handle non-standard '#' from relative json pointer
|
|
411
|
+
const maybeIndex = token.slice(1);
|
|
412
|
+
if (RE_INT.test(maybeIndex) && Object.hasOwn(val, maybeIndex)) {
|
|
413
|
+
return Number(maybeIndex);
|
|
414
|
+
} else {
|
|
415
|
+
throw new JSONPointerIndexError(`index out of range '${JSONPointer.encode(this.tokens.slice(0, idx + 1))}'`);
|
|
416
|
+
}
|
|
375
417
|
} else {
|
|
376
418
|
throw new JSONPointerIndexError(`index out of range '${JSONPointer.encode(this.tokens.slice(0, idx + 1))}'`);
|
|
377
419
|
}
|
|
378
420
|
} else if (isObject(val)) {
|
|
379
421
|
if (Object.hasOwn(val, token)) {
|
|
380
422
|
return val[token];
|
|
423
|
+
} else if (token.startsWith("#") && Object.hasOwn(val, token.slice(1))) {
|
|
424
|
+
// handle non-standard '#' from relative json pointer
|
|
425
|
+
return token.slice(1);
|
|
381
426
|
} else {
|
|
382
427
|
throw new JSONPointerKeyError(`no such property '${JSONPointer.encode(this.tokens.slice(0, idx + 1))}'`);
|
|
383
428
|
}
|
|
@@ -397,10 +442,12 @@ class JSONPointer {
|
|
|
397
442
|
|
|
398
443
|
/**
|
|
399
444
|
* Join this pointer with _tokens_.
|
|
445
|
+
*
|
|
400
446
|
* @param tokens - JSON Pointer strings, possibly without leading slashes.
|
|
401
447
|
* If a token or "part" does have a leading slash, the previous pointer is
|
|
402
448
|
* ignored and a new `JSONPointer` is created, then processing of the
|
|
403
449
|
* remaining tokens continues.
|
|
450
|
+
*
|
|
404
451
|
* @returns A new JSON Pointer that is the concatenation of all tokens or
|
|
405
452
|
* "parts".
|
|
406
453
|
*/
|
|
@@ -450,8 +497,111 @@ class JSONPointer {
|
|
|
450
497
|
}
|
|
451
498
|
return new JSONPointer(JSONPointer.encode(this.tokens.slice(0, this.tokens.length - 1)));
|
|
452
499
|
}
|
|
500
|
+
to(rel) {
|
|
501
|
+
const relativePointer = isString(rel) ? new RelativeJSONPointer(rel) : rel;
|
|
502
|
+
return relativePointer.to(this);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const RE_RELATIVE_POINTER = /(?<ORIGIN>\d+)(?<INDEX_G>(?<SIGN>[+-])(?<INDEX>\d))?(?<POINTER>.*)/s;
|
|
506
|
+
const RE_INT = /(0|[1-9][0-9]*)/;
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* A relative JSON Pointer.
|
|
510
|
+
*
|
|
511
|
+
* See https://www.ietf.org/id/draft-hha-relative-json-pointer-00.html
|
|
512
|
+
*/
|
|
513
|
+
class RelativeJSONPointer {
|
|
514
|
+
/**
|
|
515
|
+
*
|
|
516
|
+
* @param rel -
|
|
517
|
+
*/
|
|
518
|
+
constructor(rel) {
|
|
519
|
+
[this.origin, this.index, this.pointer] = this.parse(rel);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
*
|
|
524
|
+
* @returns
|
|
525
|
+
*/
|
|
526
|
+
toString() {
|
|
527
|
+
const sign = this.index > 0 ? "+" : "";
|
|
528
|
+
const index = this.index === 0 ? "" : `${sign}${this.index}`;
|
|
529
|
+
return `${this.origin}${index}${this.pointer}`;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
*
|
|
534
|
+
* @param pointer -
|
|
535
|
+
*/
|
|
536
|
+
to(pointer) {
|
|
537
|
+
const p = isString(pointer) ? new JSONPointer(pointer) : pointer;
|
|
538
|
+
|
|
539
|
+
// move to origin
|
|
540
|
+
if (this.origin > p.tokens.length) {
|
|
541
|
+
throw new JSONPointerIndexError(`origin (${this.origin}) exceeds root (${p.tokens.length})`);
|
|
542
|
+
}
|
|
543
|
+
const tokens = this.origin < 1 ? p.tokens.slice() : p.tokens.slice(0, -this.origin);
|
|
544
|
+
|
|
545
|
+
// array index offset
|
|
546
|
+
if (this.index && tokens.length && this.isIntLike(tokens.at(-1))) {
|
|
547
|
+
const newIndex = Number(tokens.at(-1)) + this.index;
|
|
548
|
+
if (newIndex < 0) {
|
|
549
|
+
throw new JSONPointerIndexError(`index offset out of range (${newIndex})`);
|
|
550
|
+
}
|
|
551
|
+
tokens[tokens.length - 1] = String(newIndex);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// pointer or index/property
|
|
555
|
+
if (this.pointer instanceof JSONPointer) {
|
|
556
|
+
tokens.push(...this.pointer.tokens);
|
|
557
|
+
} else {
|
|
558
|
+
tokens[tokens.length - 1] = `#${tokens[tokens.length - 1]}`;
|
|
559
|
+
}
|
|
560
|
+
return new JSONPointer(JSONPointer.encode(tokens));
|
|
561
|
+
}
|
|
562
|
+
parse(rel) {
|
|
563
|
+
const match = RE_RELATIVE_POINTER.exec(rel);
|
|
564
|
+
if (!match || !match.groups) {
|
|
565
|
+
throw new JSONPointerSyntaxError("failed to parse relative pointer");
|
|
566
|
+
}
|
|
453
567
|
|
|
454
|
-
|
|
568
|
+
// steps to move
|
|
569
|
+
const origin = this.parseInt(match.groups.ORIGIN);
|
|
570
|
+
|
|
571
|
+
// optional index manipulation
|
|
572
|
+
let index = 0;
|
|
573
|
+
if (match.groups["INDEX_G"]) {
|
|
574
|
+
index = this.parseInt(match.groups.INDEX);
|
|
575
|
+
if (index === 0) {
|
|
576
|
+
throw new JSONPointerSyntaxError("index offset can't be zero");
|
|
577
|
+
}
|
|
578
|
+
if (match.groups.SIGN === "-") {
|
|
579
|
+
index = -index;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// pointer or '#'. an empty string is OK.
|
|
584
|
+
if (match.groups.POINTER === "#") {
|
|
585
|
+
return [origin, index, "#"];
|
|
586
|
+
}
|
|
587
|
+
return [origin, index, new JSONPointer(match.groups.POINTER)];
|
|
588
|
+
}
|
|
589
|
+
parseInt(s) {
|
|
590
|
+
if (s.startsWith("0") && s.length > 1) {
|
|
591
|
+
throw new JSONPointerSyntaxError("unexpected leading zero");
|
|
592
|
+
}
|
|
593
|
+
if (RE_INT.test(s)) {
|
|
594
|
+
return Number(s);
|
|
595
|
+
}
|
|
596
|
+
throw new JSONPointerSyntaxError(`expected an integer, found '${s}'`);
|
|
597
|
+
}
|
|
598
|
+
isIntLike(value) {
|
|
599
|
+
if (value === undefined || isNumber(value)) {
|
|
600
|
+
return true;
|
|
601
|
+
} else {
|
|
602
|
+
return RE_INT.test(value);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
455
605
|
}
|
|
456
606
|
|
|
457
607
|
/**
|
|
@@ -486,6 +636,7 @@ var index$3 = /*#__PURE__*/Object.freeze({
|
|
|
486
636
|
JSONPointerResolutionError: JSONPointerResolutionError,
|
|
487
637
|
JSONPointerSyntaxError: JSONPointerSyntaxError,
|
|
488
638
|
JSONPointerTypeError: JSONPointerTypeError,
|
|
639
|
+
RelativeJSONPointer: RelativeJSONPointer,
|
|
489
640
|
UNDEFINED: UNDEFINED,
|
|
490
641
|
resolve: resolve
|
|
491
642
|
});
|
|
@@ -783,7 +934,7 @@ class FunctionExtension extends FilterExpression {
|
|
|
783
934
|
this.args = args;
|
|
784
935
|
}
|
|
785
936
|
evaluate(context) {
|
|
786
|
-
const func = context.environment.
|
|
937
|
+
const func = context.environment.functionRegister.get(this.name);
|
|
787
938
|
if (!func) {
|
|
788
939
|
throw new UndefinedFilterFunctionError(`filter function '${this.name}' is undefined`, this.token);
|
|
789
940
|
}
|
|
@@ -803,13 +954,6 @@ function isTruthy(value) {
|
|
|
803
954
|
if (value instanceof JSONPathNodeList && value.empty()) return false;
|
|
804
955
|
return !(typeof value === "boolean" && value === false);
|
|
805
956
|
}
|
|
806
|
-
|
|
807
|
-
/**
|
|
808
|
-
*
|
|
809
|
-
* @param left -
|
|
810
|
-
* @param operator -
|
|
811
|
-
* @param right -
|
|
812
|
-
*/
|
|
813
957
|
function compare(left, operator, right) {
|
|
814
958
|
switch (operator) {
|
|
815
959
|
case "==":
|
|
@@ -863,7 +1007,8 @@ var expression = /*#__PURE__*/Object.freeze({
|
|
|
863
1007
|
PrefixExpression: PrefixExpression,
|
|
864
1008
|
RelativeQuery: RelativeQuery,
|
|
865
1009
|
RootQuery: RootQuery,
|
|
866
|
-
StringLiteral: StringLiteral
|
|
1010
|
+
StringLiteral: StringLiteral,
|
|
1011
|
+
compare: compare
|
|
867
1012
|
});
|
|
868
1013
|
|
|
869
1014
|
class Count {
|
|
@@ -1094,7 +1239,7 @@ class TokenStream {
|
|
|
1094
1239
|
|
|
1095
1240
|
// These regular expressions are to be used with Lexer.acceptMatchRun(),
|
|
1096
1241
|
// which expects the sticky flag to be set.
|
|
1097
|
-
const exponentPattern = /e[+-]
|
|
1242
|
+
const exponentPattern = /e[+-]?\d+/y;
|
|
1098
1243
|
const functionNamePattern = /[a-z][a-z_0-9]*/y;
|
|
1099
1244
|
const indexPattern = /-?\d+/y;
|
|
1100
1245
|
const intPattern = /-?[0-9]+/y;
|
|
@@ -1387,6 +1532,10 @@ function lexInsideFilter(l) {
|
|
|
1387
1532
|
case "":
|
|
1388
1533
|
case "]":
|
|
1389
1534
|
l.filterLevel -= 1;
|
|
1535
|
+
if (l.parenStack.length === 1) {
|
|
1536
|
+
l.error("unbalanced parentheses");
|
|
1537
|
+
return null;
|
|
1538
|
+
}
|
|
1390
1539
|
l.backup();
|
|
1391
1540
|
return lexInsideBracketedSelection;
|
|
1392
1541
|
case ",":
|
|
@@ -1616,7 +1765,7 @@ class IndexSelector extends JSONPathSelector {
|
|
|
1616
1765
|
this.environment = environment;
|
|
1617
1766
|
this.token = token;
|
|
1618
1767
|
this.index = index;
|
|
1619
|
-
if (index < this.environment.
|
|
1768
|
+
if (index < this.environment.minIntIndex || index > this.environment.maxIntIndex) {
|
|
1620
1769
|
throw new JSONPathIndexError("index out of range", this.token);
|
|
1621
1770
|
}
|
|
1622
1771
|
}
|
|
@@ -1671,15 +1820,11 @@ class SliceSelector extends JSONPathSelector {
|
|
|
1671
1820
|
indices[_key] = arguments[_key];
|
|
1672
1821
|
}
|
|
1673
1822
|
for (const index of indices) {
|
|
1674
|
-
if (index !== undefined && (index < this.environment.
|
|
1823
|
+
if (index !== undefined && (index < this.environment.minIntIndex || index > this.environment.maxIntIndex)) {
|
|
1675
1824
|
throw new JSONPathIndexError("index out of range", this.token);
|
|
1676
1825
|
}
|
|
1677
1826
|
}
|
|
1678
1827
|
}
|
|
1679
|
-
normalizedIndex(length, index) {
|
|
1680
|
-
if (index < 0 && length >= Math.abs(index)) return Math.min(length + index, length - 1);
|
|
1681
|
-
return Math.min(index, length - 1);
|
|
1682
|
-
}
|
|
1683
1828
|
|
|
1684
1829
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
1685
1830
|
slice(arr, start, stop, step) {
|
|
@@ -1763,17 +1908,21 @@ class RecursiveDescentSegment extends JSONPathSelector {
|
|
|
1763
1908
|
return "..";
|
|
1764
1909
|
}
|
|
1765
1910
|
visit(node) {
|
|
1911
|
+
let depth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
|
|
1912
|
+
if (depth >= this.environment.maxRecursionDepth) {
|
|
1913
|
+
throw new JSONPathRecursionLimitError("recursion limit reached", this.token);
|
|
1914
|
+
}
|
|
1766
1915
|
const rv = [];
|
|
1767
1916
|
if (node.value instanceof String) return new JSONPathNodeList(rv);
|
|
1768
1917
|
if (isArray(node.value)) {
|
|
1769
1918
|
for (let i = 0; i < node.value.length; i++) {
|
|
1770
1919
|
const _node = new JSONPathNode(node.value[i], node.location.concat(i), node.root);
|
|
1771
|
-
rv.push(_node, ...this.visit(_node));
|
|
1920
|
+
rv.push(_node, ...this.visit(_node, depth + 1));
|
|
1772
1921
|
}
|
|
1773
1922
|
} else if (isObject(node.value)) {
|
|
1774
1923
|
for (const [key, value] of Object.entries(node.value)) {
|
|
1775
1924
|
const _node = new JSONPathNode(value, node.location.concat(key), node.root);
|
|
1776
|
-
rv.push(_node, ...this.visit(_node));
|
|
1925
|
+
rv.push(_node, ...this.visit(_node, depth + 1));
|
|
1777
1926
|
}
|
|
1778
1927
|
}
|
|
1779
1928
|
return new JSONPathNodeList(rv);
|
|
@@ -1883,6 +2032,18 @@ class JSONPath {
|
|
|
1883
2032
|
return nodes;
|
|
1884
2033
|
}
|
|
1885
2034
|
|
|
2035
|
+
/**
|
|
2036
|
+
* Return a {@link JSONPathNode} instance for the first object found in
|
|
2037
|
+
* _value_ matching this query.
|
|
2038
|
+
*
|
|
2039
|
+
* @param value - JSON-like data to which this query will be applied.
|
|
2040
|
+
* @returns The first node in _value_ matching this query, or `undefined` if
|
|
2041
|
+
* there are no matches.
|
|
2042
|
+
*/
|
|
2043
|
+
match(value) {
|
|
2044
|
+
return this.query(value).nodes.at(0);
|
|
2045
|
+
}
|
|
2046
|
+
|
|
1886
2047
|
/**
|
|
1887
2048
|
*
|
|
1888
2049
|
*/
|
|
@@ -2044,7 +2205,7 @@ class Parser {
|
|
|
2044
2205
|
const tok = stream.next();
|
|
2045
2206
|
const expr = this.parseFilterExpression(stream);
|
|
2046
2207
|
if (expr instanceof FunctionExtension) {
|
|
2047
|
-
const func = this.environment.
|
|
2208
|
+
const func = this.environment.functionRegister.get(expr.name);
|
|
2048
2209
|
if (func && func.returnType === FunctionExpressionType.ValueType) {
|
|
2049
2210
|
throw new JSONPathTypeError(`result of ${expr.name}() must be compared`, expr.token);
|
|
2050
2211
|
}
|
|
@@ -2114,7 +2275,16 @@ class Parser {
|
|
|
2114
2275
|
if (!func) {
|
|
2115
2276
|
throw new JSONPathSyntaxError(`unexpected '${stream.current.value}'`, stream.current);
|
|
2116
2277
|
}
|
|
2117
|
-
|
|
2278
|
+
let expr = func.bind(this)(stream);
|
|
2279
|
+
|
|
2280
|
+
// Could be a comparison/logical expression
|
|
2281
|
+
let peekKind = stream.peek.kind;
|
|
2282
|
+
while (BINARY_OPERATORS.has(peekKind)) {
|
|
2283
|
+
stream.next();
|
|
2284
|
+
expr = this.parseInfixExpression(stream, expr);
|
|
2285
|
+
peekKind = stream.peek.kind;
|
|
2286
|
+
}
|
|
2287
|
+
args.push(expr);
|
|
2118
2288
|
if (stream.peek.kind !== TokenKind.RPAREN) {
|
|
2119
2289
|
if (stream.peek.kind === TokenKind.RBRACKET) break;
|
|
2120
2290
|
stream.expectPeek(TokenKind.COMMA);
|
|
@@ -2122,6 +2292,7 @@ class Parser {
|
|
|
2122
2292
|
}
|
|
2123
2293
|
stream.next();
|
|
2124
2294
|
}
|
|
2295
|
+
stream.expect(TokenKind.RPAREN);
|
|
2125
2296
|
return new FunctionExtension(tok, tok.value, this.environment.checkWellTypedness(tok, args));
|
|
2126
2297
|
}
|
|
2127
2298
|
parseFilterExpression(stream) {
|
|
@@ -2166,7 +2337,7 @@ class Parser {
|
|
|
2166
2337
|
}
|
|
2167
2338
|
throwForNonComparableFunction(expr) {
|
|
2168
2339
|
if (!(expr instanceof FunctionExtension)) return;
|
|
2169
|
-
const func = this.environment.
|
|
2340
|
+
const func = this.environment.functionRegister.get(expr.name);
|
|
2170
2341
|
if (func && func.returnType !== FunctionExpressionType.ValueType) {
|
|
2171
2342
|
throw new JSONPathTypeError(`result of ${expr.name}() is not comparable`, expr.token);
|
|
2172
2343
|
}
|
|
@@ -2174,30 +2345,53 @@ class Parser {
|
|
|
2174
2345
|
}
|
|
2175
2346
|
|
|
2176
2347
|
/**
|
|
2177
|
-
*
|
|
2348
|
+
* JSONPath environment options. The defaults are in compliance with JSONPath
|
|
2349
|
+
* standards.
|
|
2178
2350
|
*/
|
|
2179
2351
|
|
|
2180
|
-
const defaultOptions = {
|
|
2181
|
-
strict: true,
|
|
2182
|
-
maxIntIndex: Math.pow(2, 53) - 1,
|
|
2183
|
-
minIntIndex: -Math.pow(2, 53) - 1
|
|
2184
|
-
};
|
|
2185
|
-
|
|
2186
2352
|
/**
|
|
2187
2353
|
*
|
|
2188
2354
|
*/
|
|
2189
2355
|
class JSONPathEnvironment {
|
|
2190
2356
|
/**
|
|
2357
|
+
* Indicates if the environment should to be strict about its compliance with
|
|
2358
|
+
* JSONPath standards.
|
|
2191
2359
|
*
|
|
2360
|
+
* Defaults to `true`. Setting `strict` to `false` currently has no effect.
|
|
2361
|
+
* If/when we add non-standard features, the environment's strictness will
|
|
2362
|
+
* control their availability.
|
|
2363
|
+
*/
|
|
2364
|
+
|
|
2365
|
+
/**
|
|
2366
|
+
* The maximum number allowed when indexing or slicing an array. Defaults to
|
|
2367
|
+
* 2**53 -1.
|
|
2368
|
+
*/
|
|
2369
|
+
|
|
2370
|
+
/**
|
|
2371
|
+
* The minimum number allowed when indexing or slicing an array. Defaults to
|
|
2372
|
+
* -(2**53) -1.
|
|
2373
|
+
*/
|
|
2374
|
+
|
|
2375
|
+
/**
|
|
2376
|
+
* The maximum number of objects and/or arrays the recursive descent selector
|
|
2377
|
+
* can visit before a `JSONPathRecursionLimitError` is thrown.
|
|
2378
|
+
*/
|
|
2379
|
+
|
|
2380
|
+
/**
|
|
2381
|
+
* A map of function names to objects implementing the {@link FilterFunction}
|
|
2382
|
+
* interface. You are free to set or delete custom filter functions directly.
|
|
2192
2383
|
*/
|
|
2193
|
-
|
|
2384
|
+
functionRegister = new Map();
|
|
2194
2385
|
/**
|
|
2195
2386
|
*
|
|
2196
2387
|
* @param options -
|
|
2197
2388
|
*/
|
|
2198
2389
|
constructor() {
|
|
2199
|
-
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] :
|
|
2200
|
-
this.
|
|
2390
|
+
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
2391
|
+
this.strict = options.strict ?? true;
|
|
2392
|
+
this.maxIntIndex = options.maxIntIndex ?? Math.pow(2, 53) - 1;
|
|
2393
|
+
this.minIntIndex = options.maxIntIndex ?? -Math.pow(2, 53) - 1;
|
|
2394
|
+
this.maxRecursionDepth = options.maxRecursionDepth ?? 50;
|
|
2201
2395
|
this.parser = new Parser(this);
|
|
2202
2396
|
this.setupFilterFunctions();
|
|
2203
2397
|
}
|
|
@@ -2220,12 +2414,25 @@ class JSONPathEnvironment {
|
|
|
2220
2414
|
query(path, value) {
|
|
2221
2415
|
return this.compile(path).query(value);
|
|
2222
2416
|
}
|
|
2417
|
+
|
|
2418
|
+
/**
|
|
2419
|
+
* Return a {@link JSONPathNode} instance for the first object found in
|
|
2420
|
+
* _value_ matching _path_.
|
|
2421
|
+
*
|
|
2422
|
+
* @param path - A JSONPath query.
|
|
2423
|
+
* @param value - JSON-like data to which the query _path_ will be applied.
|
|
2424
|
+
* @returns The first node in _value_ matching _path_, or `undefined` if
|
|
2425
|
+
* there are no matches.
|
|
2426
|
+
*/
|
|
2427
|
+
match(path, value) {
|
|
2428
|
+
return this.compile(path).match(value);
|
|
2429
|
+
}
|
|
2223
2430
|
setupFilterFunctions() {
|
|
2224
|
-
this.
|
|
2225
|
-
this.
|
|
2226
|
-
this.
|
|
2227
|
-
this.
|
|
2228
|
-
this.
|
|
2431
|
+
this.functionRegister.set("count", new Count());
|
|
2432
|
+
this.functionRegister.set("length", new Length());
|
|
2433
|
+
this.functionRegister.set("search", new Search());
|
|
2434
|
+
this.functionRegister.set("match", new Match());
|
|
2435
|
+
this.functionRegister.set("value", new Value());
|
|
2229
2436
|
}
|
|
2230
2437
|
|
|
2231
2438
|
/**
|
|
@@ -2235,7 +2442,7 @@ class JSONPathEnvironment {
|
|
|
2235
2442
|
*/
|
|
2236
2443
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
2237
2444
|
checkWellTypedness(token, args) {
|
|
2238
|
-
const func = this.
|
|
2445
|
+
const func = this.functionRegister.get(token.value);
|
|
2239
2446
|
if (!func) {
|
|
2240
2447
|
throw new UndefinedFilterFunctionError(`no such function '${token.value}'`, token);
|
|
2241
2448
|
}
|
|
@@ -2249,17 +2456,17 @@ class JSONPathEnvironment {
|
|
|
2249
2456
|
for (const [typ, arg, idx] of func.argTypes.map((t, i) => [t, args[i], i])) {
|
|
2250
2457
|
switch (typ) {
|
|
2251
2458
|
case FunctionExpressionType.ValueType:
|
|
2252
|
-
if (!(arg instanceof FilterExpressionLiteral || arg instanceof JSONPathQuery && arg.path.singularQuery())) {
|
|
2459
|
+
if (!(arg instanceof FilterExpressionLiteral || arg instanceof JSONPathQuery && arg.path.singularQuery() || arg instanceof FunctionExtension && this.functionRegister.get(arg.name)?.returnType === FunctionExpressionType.ValueType)) {
|
|
2253
2460
|
throw new JSONPathTypeError(`${token.value}() argument ${idx} must be of ValueType`, arg.token);
|
|
2254
2461
|
}
|
|
2255
2462
|
break;
|
|
2256
2463
|
case FunctionExpressionType.LogicalType:
|
|
2257
|
-
if (!(arg instanceof
|
|
2464
|
+
if (!(arg instanceof JSONPathQuery || arg instanceof InfixExpression)) {
|
|
2258
2465
|
throw new JSONPathTypeError(`${token.value}() argument ${idx} must be of LogicalType`, arg.token);
|
|
2259
2466
|
}
|
|
2260
2467
|
break;
|
|
2261
2468
|
case FunctionExpressionType.NodesType:
|
|
2262
|
-
if (!(arg instanceof JSONPathQuery)) {
|
|
2469
|
+
if (!(arg instanceof JSONPathQuery || arg instanceof FunctionExtension && this.functionRegister.get(arg.name)?.returnType === FunctionExpressionType.NodesType)) {
|
|
2263
2470
|
throw new JSONPathTypeError(`${token.value}() argument ${idx} must be of NodesType`, arg.token);
|
|
2264
2471
|
}
|
|
2265
2472
|
}
|
|
@@ -2314,6 +2521,19 @@ function compile(path) {
|
|
|
2314
2521
|
return DEFAULT_ENVIRONMENT.compile(path);
|
|
2315
2522
|
}
|
|
2316
2523
|
|
|
2524
|
+
/**
|
|
2525
|
+
* Return a {@link JSONPathNode} instance for the first object found in
|
|
2526
|
+
* _value_ matching _path_.
|
|
2527
|
+
*
|
|
2528
|
+
* @param path - A JSONPath query.
|
|
2529
|
+
* @param value - JSON-like data to which the query _path_ will be applied.
|
|
2530
|
+
* @returns The first node in _value_ matching _path_, or `undefined` if
|
|
2531
|
+
* there are no matches.
|
|
2532
|
+
*/
|
|
2533
|
+
function match(path, value) {
|
|
2534
|
+
return DEFAULT_ENVIRONMENT.match(path, value);
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2317
2537
|
var index$1 = /*#__PURE__*/Object.freeze({
|
|
2318
2538
|
__proto__: null,
|
|
2319
2539
|
DEFAULT_ENVIRONMENT: DEFAULT_ENVIRONMENT,
|
|
@@ -2325,6 +2545,7 @@ var index$1 = /*#__PURE__*/Object.freeze({
|
|
|
2325
2545
|
JSONPathLexerError: JSONPathLexerError,
|
|
2326
2546
|
JSONPathNode: JSONPathNode,
|
|
2327
2547
|
JSONPathNodeList: JSONPathNodeList,
|
|
2548
|
+
JSONPathRecursionLimitError: JSONPathRecursionLimitError,
|
|
2328
2549
|
JSONPathSyntaxError: JSONPathSyntaxError,
|
|
2329
2550
|
JSONPathTypeError: JSONPathTypeError,
|
|
2330
2551
|
Nothing: Nothing,
|
|
@@ -2333,6 +2554,7 @@ var index$1 = /*#__PURE__*/Object.freeze({
|
|
|
2333
2554
|
compile: compile,
|
|
2334
2555
|
expressions: expression,
|
|
2335
2556
|
functions: index$2,
|
|
2557
|
+
match: match,
|
|
2336
2558
|
query: query,
|
|
2337
2559
|
selectors: selectors
|
|
2338
2560
|
});
|
|
@@ -2633,6 +2855,15 @@ class JSONPatch {
|
|
|
2633
2855
|
}
|
|
2634
2856
|
}
|
|
2635
2857
|
|
|
2858
|
+
/**
|
|
2859
|
+
* @returns an iterator over ops in this patch.
|
|
2860
|
+
*/
|
|
2861
|
+
*[Symbol.iterator]() {
|
|
2862
|
+
for (const op of this.ops) {
|
|
2863
|
+
yield op.toObject();
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2636
2867
|
/**
|
|
2637
2868
|
*
|
|
2638
2869
|
* @param path -
|
|
@@ -2808,8 +3039,9 @@ var index = /*#__PURE__*/Object.freeze({
|
|
|
2808
3039
|
apply: apply
|
|
2809
3040
|
});
|
|
2810
3041
|
|
|
2811
|
-
const version = "0.
|
|
3042
|
+
const version = "0.2.0";
|
|
2812
3043
|
|
|
3044
|
+
exports.DEFAULT_ENVIRONMENT = DEFAULT_ENVIRONMENT;
|
|
2813
3045
|
exports.FunctionExpressionType = FunctionExpressionType;
|
|
2814
3046
|
exports.JSONPatch = JSONPatch;
|
|
2815
3047
|
exports.JSONPatchError = JSONPatchError;
|
|
@@ -2821,10 +3053,12 @@ exports.JSONPathIndexError = JSONPathIndexError;
|
|
|
2821
3053
|
exports.JSONPathLexerError = JSONPathLexerError;
|
|
2822
3054
|
exports.JSONPathNode = JSONPathNode;
|
|
2823
3055
|
exports.JSONPathNodeList = JSONPathNodeList;
|
|
3056
|
+
exports.JSONPathRecursionLimitError = JSONPathRecursionLimitError;
|
|
2824
3057
|
exports.JSONPathSyntaxError = JSONPathSyntaxError;
|
|
2825
3058
|
exports.JSONPathTypeError = JSONPathTypeError;
|
|
2826
3059
|
exports.JSONPointer = JSONPointer;
|
|
2827
3060
|
exports.Nothing = Nothing;
|
|
3061
|
+
exports.RelativeJSONPointer = RelativeJSONPointer;
|
|
2828
3062
|
exports.Token = Token;
|
|
2829
3063
|
exports.TokenKind = TokenKind;
|
|
2830
3064
|
exports.UNDEFINED = UNDEFINED;
|