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