json-p3 2.0.0 → 2.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.
@@ -1,5 +1,5 @@
1
1
  /*
2
- * json-p3 version 2.0.0
2
+ * json-p3 version 2.1.0
3
3
  * https://github.com/jg-rp/json-p3
4
4
  *
5
5
  * MIT License
@@ -651,6 +651,30 @@ var index$3 = /*#__PURE__*/Object.freeze({
651
651
  resolve: resolve
652
652
  });
653
653
 
654
+ /**
655
+ * An identifier that is allowed in both JS and JSONPath.
656
+ * JSONPath identifiers are generally much more permissive than JS ones, but
657
+ * they don't allow the character "$", so we take the intersection of the two
658
+ * when deciding whether to use dot shorthand for canonical serialization of
659
+ * simple names.
660
+ */
661
+ const SHORTHAND_COMPATIBLE_IDENTIFIER = /^[\p{ID_Start}_]\p{ID_Continue}*$/u;
662
+
663
+ /** Usable in a quoted path. */
664
+ function toQuoted(name) {
665
+ return name.includes("'") && !name.includes('"') ? JSON.stringify(name) : toCanonical(name);
666
+ }
667
+
668
+ /** Usable in a normalized path. */
669
+ function toCanonical(name) {
670
+ return `'${JSON.stringify(name).slice(1, -1).replaceAll('\\"', '"').replaceAll("'", "\\'")}'`;
671
+ }
672
+
673
+ /** Usable in a shorthand path. */
674
+ function toShorthand(name) {
675
+ return SHORTHAND_COMPATIBLE_IDENTIFIER.test(name) ? name : null;
676
+ }
677
+
654
678
  const Nothing = Symbol.for("jsonpath.nothing");
655
679
 
656
680
  /**
@@ -658,7 +682,7 @@ const Nothing = Symbol.for("jsonpath.nothing");
658
682
  */
659
683
 
660
684
  /**
661
- * Object passed to FilterExpression.evaluate().
685
+ * Object passed to `FilterExpression.evaluate()`.
662
686
  */
663
687
 
664
688
  /**
@@ -669,6 +693,14 @@ function hasStringKey(value, key) {
669
693
  }
670
694
  const KEY_MARK = "\x02";
671
695
 
696
+ /**
697
+ * Options for serializing paths.
698
+ */
699
+
700
+ const defaultSerializationOptions = {
701
+ form: "pretty"
702
+ };
703
+
672
704
  /**
673
705
  * The pair of a JSON value and its location found in the target JSON value.
674
706
  */
@@ -683,10 +715,31 @@ class JSONPathNode {
683
715
  this.location = location;
684
716
  this.root = root;
685
717
  }
718
+
719
+ /**
720
+ * @deprecated Use {@link getPath} with `options.form` set to `canonical` instead.
721
+ */
686
722
  get path() {
723
+ return this.getPath({
724
+ form: "canonical"
725
+ });
726
+ }
727
+
728
+ /**
729
+ * Get the path to this node in the target JSON value.
730
+ *
731
+ * Given that the path refers to the singular current node, the returned path
732
+ * will always be a normalized path if `options.form` is set to `canonical`,
733
+ * following section 2.7 of RFC 9535.
734
+ */
735
+ getPath(options) {
736
+ const opts = {
737
+ ...defaultSerializationOptions,
738
+ ...options
739
+ };
687
740
  return (
688
741
  // eslint-disable-next-line prefer-template
689
- "$" + this.location.map(s => isString(s) ? this.decode_name_location(s) : `[${s}]`).join("")
742
+ "$" + this.location.map(s => isString(s) ? this.decodeNameLocation(s, opts) : `[${s}]`).join("")
690
743
  );
691
744
  }
692
745
 
@@ -699,8 +752,16 @@ class JSONPathNode {
699
752
  }
700
753
  return new JSONPointer(JSONPointer.encode(this.location.map(String)));
701
754
  }
702
- decode_name_location(name) {
703
- return name.startsWith(KEY_MARK) ? `[~'${name.slice(1).replaceAll("'", "\\'")}']` : `['${name.replaceAll("'", "\\'")}']`;
755
+ decodeNameLocation(name, options) {
756
+ const normalized = options.form === "canonical";
757
+ const serialize = normalized ? toCanonical : toQuoted;
758
+ const hasKeyMark = name.startsWith(KEY_MARK);
759
+ if (hasKeyMark) name = name.slice(1);
760
+ const shorthand = toShorthand(name);
761
+ if (hasKeyMark) {
762
+ return normalized || shorthand == null ? `[~${serialize(name)}]` : `.~${shorthand}`;
763
+ }
764
+ return normalized || shorthand == null ? `[${serialize(name)}]` : `.${shorthand}`;
704
765
  }
705
766
  }
706
767
 
@@ -761,8 +822,8 @@ class JSONPathNodeList {
761
822
  * A normalized path contains only property name and index selectors, and
762
823
  * always uses bracketed segments, never shorthand selectors.
763
824
  */
764
- paths() {
765
- return this.nodes.map(node => node.path);
825
+ paths(options) {
826
+ return this.nodes.map(node => node.getPath(options));
766
827
  }
767
828
 
768
829
  /**
@@ -834,7 +895,7 @@ class StringLiteral extends FilterExpressionLiteral {
834
895
  return this.value;
835
896
  }
836
897
  toString() {
837
- return JSON.stringify(this.value);
898
+ return toCanonical(this.value);
838
899
  }
839
900
  }
840
901
  class NumberLiteral extends FilterExpressionLiteral {
@@ -865,10 +926,13 @@ class PrefixExpression extends FilterExpression {
865
926
  }
866
927
  throw new JSONPathTypeError(`unknown operator '${this.operator}'`, this.token);
867
928
  }
868
- toString() {
869
- return `${this.operator}${this.right.toString()}`;
929
+ toString(options) {
930
+ return `${this.operator}${this.right.toString(options)}`;
870
931
  }
871
932
  }
933
+ const PRECEDENCE_LOGICAL_OR$1 = 4;
934
+ const PRECEDENCE_LOGICAL_AND$1 = 5;
935
+ const PRECEDENCE_PREFIX$1 = 7;
872
936
  class InfixExpression extends FilterExpression {
873
937
  constructor(token, left, operator, right) {
874
938
  super(token);
@@ -891,11 +955,12 @@ class InfixExpression extends FilterExpression {
891
955
  }
892
956
  return compare(left, this.operator, right);
893
957
  }
894
- toString() {
958
+ toString(options) {
959
+ // Note that `LogicalExpression.toString()` does not call this.
895
960
  if (this.logical) {
896
- return `(${this.left.toString()} ${this.operator} ${this.right.toString()})`;
961
+ return `(${this.left.toString(options)} ${this.operator} ${this.right.toString(options)})`;
897
962
  }
898
- return `${this.left.toString()} ${this.operator} ${this.right.toString()}`;
963
+ return `${this.left.toString(options)} ${this.operator} ${this.right.toString(options)}`;
899
964
  }
900
965
  }
901
966
  class LogicalExpression extends FilterExpression {
@@ -909,8 +974,38 @@ class LogicalExpression extends FilterExpression {
909
974
  if (value instanceof JSONPathNodeList) return value.nodes.length > 0; // existence
910
975
  return isTruthy(value);
911
976
  }
912
- toString() {
913
- return this.expression.toString();
977
+ toString(options) {
978
+ // Minimize parentheses in logical expressions.
979
+ function _toString(expression, parentPrecedence) {
980
+ if (expression instanceof InfixExpression) {
981
+ let precedence;
982
+ let op;
983
+ let left;
984
+ let right;
985
+ if (expression.operator === "&&") {
986
+ precedence = PRECEDENCE_LOGICAL_AND$1;
987
+ op = "&&";
988
+ left = _toString(expression.left, precedence);
989
+ right = _toString(expression.right, precedence);
990
+ } else if (expression.operator === "||") {
991
+ precedence = PRECEDENCE_LOGICAL_OR$1;
992
+ op = "||";
993
+ left = _toString(expression.left, precedence);
994
+ right = _toString(expression.right, precedence);
995
+ } else {
996
+ return expression.toString(options);
997
+ }
998
+ const expr = `${left} ${op} ${right}`;
999
+ return precedence < parentPrecedence ? `(${expr})` : expr;
1000
+ }
1001
+ if (expression instanceof PrefixExpression) {
1002
+ const operand = _toString(expression.right, PRECEDENCE_PREFIX$1);
1003
+ const expr = `!${operand}`;
1004
+ return parentPrecedence > PRECEDENCE_PREFIX$1 ? `(${expr})` : expr;
1005
+ }
1006
+ return expression.toString(options);
1007
+ }
1008
+ return _toString(this.expression, 0);
914
1009
  }
915
1010
  }
916
1011
 
@@ -928,16 +1023,16 @@ class RelativeQuery extends FilterQuery {
928
1023
  evaluate(context) {
929
1024
  return context.lazy ? new JSONPathNodeList(Array.from(this.path.lazyQuery(context.currentValue))) : this.path.query(context.currentValue);
930
1025
  }
931
- toString() {
932
- return `@${this.path.toString().slice(1)}`;
1026
+ toString(options) {
1027
+ return `@${this.path.toString(options).slice(1)}`;
933
1028
  }
934
1029
  }
935
1030
  class RootQuery extends FilterQuery {
936
1031
  evaluate(context) {
937
1032
  return context.lazy ? new JSONPathNodeList(Array.from(this.path.lazyQuery(context.rootValue))) : this.path.query(context.rootValue);
938
1033
  }
939
- toString() {
940
- return this.path.toString();
1034
+ toString(options) {
1035
+ return this.path.toString(options);
941
1036
  }
942
1037
  }
943
1038
  class FunctionExtension extends FilterExpression {
@@ -955,8 +1050,8 @@ class FunctionExtension extends FilterExpression {
955
1050
  const args = this.args.map(arg => arg.evaluate(context)).map((arg, idx) => func.argTypes[idx] !== FunctionExpressionType.NodesType && arg instanceof JSONPathNodeList ? this.unpack_node_list(arg) : arg);
956
1051
  return func.call(...args);
957
1052
  }
958
- toString() {
959
- return `${this.name}(${this.args.map(e => e.toString()).join(", ")})`;
1053
+ toString(options) {
1054
+ return `${this.name}(${this.args.map(e => e.toString(options)).join(", ")})`;
960
1055
  }
961
1056
  unpack_node_list(arg) {
962
1057
  switch (arg.length) {
@@ -3163,8 +3258,17 @@ class NameSelector extends JSONPathSelector {
3163
3258
  yield new JSONPathNode(node.value[this.name], node.location.concat(this.name), node.root);
3164
3259
  }
3165
3260
  }
3166
- toString() {
3167
- return `'${this.name}'`;
3261
+ toString(options) {
3262
+ const {
3263
+ form
3264
+ } = {
3265
+ ...defaultSerializationOptions,
3266
+ ...options
3267
+ };
3268
+ return form === "canonical" ? toCanonical(this.name) : toQuoted(this.name);
3269
+ }
3270
+ shorthand() {
3271
+ return toShorthand(this.name);
3168
3272
  }
3169
3273
  }
3170
3274
 
@@ -3433,8 +3537,8 @@ class FilterSelector extends JSONPathSelector {
3433
3537
  }
3434
3538
  }
3435
3539
  }
3436
- toString() {
3437
- return `?${this.expression.toString()}`;
3540
+ toString(options) {
3541
+ return `?${this.expression.toString(options)}`;
3438
3542
  }
3439
3543
  }
3440
3544
 
@@ -3465,7 +3569,7 @@ class JSONPathSegment {
3465
3569
  */
3466
3570
 
3467
3571
  /**
3468
- * Return a canonical string representation of this segment.
3572
+ * Return a string representation of this segment.
3469
3573
  */
3470
3574
  }
3471
3575
 
@@ -3487,8 +3591,18 @@ class ChildSegment extends JSONPathSegment {
3487
3591
  }
3488
3592
  }
3489
3593
  }
3490
- toString() {
3491
- return `[${this.selectors.map(s => s.toString()).join(", ")}]`;
3594
+ toString(options) {
3595
+ const {
3596
+ form
3597
+ } = {
3598
+ ...defaultSerializationOptions,
3599
+ ...options
3600
+ };
3601
+ if (form === "pretty" && this.selectors.length === 1 && this.selectors[0] instanceof NameSelector) {
3602
+ const shorthand = this.selectors[0].shorthand();
3603
+ if (shorthand != null) return `.${shorthand}`;
3604
+ }
3605
+ return `[${this.selectors.map(s => s.toString(options)).join(", ")}]`;
3492
3606
  }
3493
3607
  }
3494
3608
 
@@ -3515,8 +3629,8 @@ class DescendantSegment extends JSONPathSegment {
3515
3629
  }
3516
3630
  }
3517
3631
  }
3518
- toString() {
3519
- return `..[${this.selectors.map(s => s.toString()).join(", ")}]`;
3632
+ toString(options) {
3633
+ return `..[${this.selectors.map(s => s.toString(options)).join(", ")}]`;
3520
3634
  }
3521
3635
  visit(node) {
3522
3636
  var _this = this;
@@ -3616,23 +3730,18 @@ function shuffle(entries) {
3616
3730
  }
3617
3731
 
3618
3732
  /**
3619
- *
3733
+ * A compiled JSONPath query ready to be applied to different data repeatedly.
3620
3734
  */
3621
3735
  class JSONPathQuery {
3622
- /**
3623
- *
3624
- * @param environment -
3625
- * @param segments -
3626
- */
3627
3736
  constructor(environment, segments) {
3628
3737
  this.environment = environment;
3629
3738
  this.segments = segments;
3630
3739
  }
3631
3740
 
3632
3741
  /**
3633
- *
3634
- * @param value -
3635
- * @returns
3742
+ * Apply this JSONPath query to _value_.
3743
+ * @param value - A JSON-like object to apply this query to.
3744
+ * @returns Nodes matched by applying this query to _value_.
3636
3745
  */
3637
3746
  query(value) {
3638
3747
  let nodes = [new JSONPathNode(value, [], value)];
@@ -3643,9 +3752,9 @@ class JSONPathQuery {
3643
3752
  }
3644
3753
 
3645
3754
  /**
3646
- *
3647
- * @param value -
3648
- * @returns
3755
+ * Apply this JSONPath query to _value_.
3756
+ * @param value - A JSON-like object to apply this query to.
3757
+ * @returns An iterator over nodes matched by applying this query to _value_.
3649
3758
  */
3650
3759
  lazyQuery(value) {
3651
3760
  let nodes = [new JSONPathNode(value, [], value)][Symbol.iterator]();
@@ -3671,11 +3780,15 @@ class JSONPathQuery {
3671
3780
  }
3672
3781
 
3673
3782
  /**
3674
- *
3783
+ * Return a string representation of this query.
3675
3784
  */
3676
- toString() {
3677
- return `$${this.segments.map(s => s.toString()).join("")}`;
3785
+ toString(options) {
3786
+ return `$${this.segments.map(s => s.toString(options)).join("")}`;
3678
3787
  }
3788
+
3789
+ /**
3790
+ * Return `true` if this query is a _singular query_, or `false` otherwise.
3791
+ */
3679
3792
  singularQuery() {
3680
3793
  for (const segment of this.segments) {
3681
3794
  if (segment instanceof DescendantSegment) return false;
@@ -3717,8 +3830,15 @@ class KeySelector extends JSONPathSelector {
3717
3830
  yield new JSONPathNode(this.key, node.location.concat(`${KEY_MARK}${this.key}`), node.root);
3718
3831
  }
3719
3832
  }
3720
- toString() {
3721
- return `~'${this.key.replaceAll("'", "\\'")}'`;
3833
+ toString(options) {
3834
+ const {
3835
+ form
3836
+ } = {
3837
+ ...defaultSerializationOptions,
3838
+ ...options
3839
+ };
3840
+ const serialize = form === "canonical" ? toCanonical : toQuoted;
3841
+ return `~${serialize(this.key)}`;
3722
3842
  }
3723
3843
  }
3724
3844
 
@@ -3794,8 +3914,8 @@ class KeysFilterSelector extends JSONPathSelector {
3794
3914
  }
3795
3915
  }
3796
3916
  }
3797
- toString() {
3798
- return `~?${this.expression.toString()}`;
3917
+ toString(options) {
3918
+ return `~?${this.expression.toString(options)}`;
3799
3919
  }
3800
3920
  }
3801
3921
 
@@ -5085,6 +5205,6 @@ var index = /*#__PURE__*/Object.freeze({
5085
5205
  apply: apply
5086
5206
  });
5087
5207
 
5088
- const version = "2.0.0";
5208
+ const version = "2.1.0";
5089
5209
 
5090
5210
  export { DEFAULT_ENVIRONMENT, FunctionExpressionType, JSONPatch, JSONPatchError, JSONPatchTestFailure, JSONPathEnvironment, JSONPathError, JSONPathIndexError, JSONPathLexerError, JSONPathNode, JSONPathNodeList, JSONPathQuery, JSONPathRecursionLimitError, JSONPathSyntaxError, JSONPathTypeError, JSONPointer, Nothing, RelativeJSONPointer, Token, TokenKind, UNDEFINED, apply, compile, index as jsonpatch, index$1 as jsonpath, index$3 as jsonpointer, lazyQuery, query, resolve, version };