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