json-p3 1.2.1 → 1.3.1

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 1.2.1
2
+ * json-p3 version 1.3.1
3
3
  * https://github.com/jg-rp/json-p3
4
4
  *
5
5
  * MIT License
@@ -639,6 +639,24 @@ var index$3 = /*#__PURE__*/Object.freeze({
639
639
  resolve: resolve
640
640
  });
641
641
 
642
+ const Nothing = Symbol.for("jsonpath.nothing");
643
+
644
+ /**
645
+ * ValueType for JSONPath function expression tye system.
646
+ */
647
+
648
+ /**
649
+ * Object passed to FilterExpression.evaluate().
650
+ */
651
+
652
+ /**
653
+ * A type predicate for an object with a string property.
654
+ */
655
+ function hasStringKey(value, key) {
656
+ return isObject(value) && Object.hasOwn(value, key);
657
+ }
658
+ const KEY_MARK = "\x02";
659
+
642
660
  /**
643
661
  * The pair of a JSON value and its location found in the target JSON value.
644
662
  */
@@ -656,7 +674,7 @@ class JSONPathNode {
656
674
  get path() {
657
675
  return (
658
676
  // eslint-disable-next-line prefer-template
659
- "$" + this.location.map(s => isString(s) ? `['${s}']` : `[${s}]`).join("")
677
+ "$" + this.location.map(s => isString(s) ? this.decode_name_location(s) : `[${s}]`).join("")
660
678
  );
661
679
  }
662
680
 
@@ -669,6 +687,9 @@ class JSONPathNode {
669
687
  }
670
688
  return new JSONPointer(JSONPointer.encode(this.location.map(String)));
671
689
  }
690
+ decode_name_location(name) {
691
+ return name.startsWith(KEY_MARK) ? `[~'${name.slice(1).replaceAll("'", "\\'")}']` : `['${name.replaceAll("'", "\\'")}']`;
692
+ }
672
693
  }
673
694
 
674
695
  /**
@@ -748,23 +769,6 @@ class JSONPathNodeList {
748
769
  }
749
770
  }
750
771
 
751
- const Nothing = Symbol.for("jsonpath.nothing");
752
-
753
- /**
754
- * ValueType for JSONPath function expression tye system.
755
- */
756
-
757
- /**
758
- * Object passed to FilterExpression.evaluate().
759
- */
760
-
761
- /**
762
- * A type predicate for an object with a string property.
763
- */
764
- function hasStringKey(value, key) {
765
- return isObject(value) && Object.hasOwn(value, key);
766
- }
767
-
768
772
  /**
769
773
  * Base class for all filter expressions.
770
774
  */
@@ -1110,9 +1114,53 @@ class Match {
1110
1114
  }
1111
1115
  fullMatch(pattern) {
1112
1116
  const parts = [];
1113
- if (!pattern.startsWith("^")) parts.push("^");
1114
- parts.push(pattern);
1115
- if (!pattern.endsWith("$")) parts.push("$");
1117
+ let nonCaptureGroup = false;
1118
+ if (!pattern.startsWith("^") && !pattern.startsWith("^(")) {
1119
+ nonCaptureGroup = true;
1120
+ parts.push("^(?:");
1121
+ }
1122
+ parts.push(this.mapRegexp(pattern));
1123
+ if (nonCaptureGroup && !pattern.endsWith("$") && !pattern.endsWith(")$")) {
1124
+ parts.push(")$");
1125
+ }
1126
+ return parts.join("");
1127
+ }
1128
+
1129
+ // See https://datatracker.ietf.org/doc/html/rfc9485#name-ecmascript-regexps
1130
+ mapRegexp(pattern) {
1131
+ let escaped = false;
1132
+ let charClass = false;
1133
+ const parts = [];
1134
+ for (const ch of pattern) {
1135
+ switch (ch) {
1136
+ case ".":
1137
+ if (!escaped && !charClass) {
1138
+ parts.push("(?:(?![\r\n])\\P{Cs}|\\p{Cs}\\p{Cs})");
1139
+ } else {
1140
+ parts.push(ch);
1141
+ escaped = false;
1142
+ }
1143
+ break;
1144
+ case "\\":
1145
+ escaped = true;
1146
+ parts.push(ch);
1147
+ break;
1148
+ case "[":
1149
+ charClass = true;
1150
+ escaped = false;
1151
+ parts.push(ch);
1152
+ break;
1153
+ case "]":
1154
+ charClass = false;
1155
+ escaped = false;
1156
+ parts.push(ch);
1157
+ break;
1158
+ default:
1159
+ escaped = false;
1160
+ parts.push(ch);
1161
+ break;
1162
+ }
1163
+ }
1116
1164
  return parts.join("");
1117
1165
  }
1118
1166
  }
@@ -1141,7 +1189,7 @@ class Search {
1141
1189
  }
1142
1190
  }
1143
1191
  try {
1144
- const re = new RegExp(pattern, "u");
1192
+ const re = new RegExp(this.mapRegexp(pattern), "u");
1145
1193
  if (this.cacheSize > 0) this.#cache.set(pattern, re);
1146
1194
  return !!s.match(re);
1147
1195
  } catch (error) {
@@ -1149,6 +1197,44 @@ class Search {
1149
1197
  return false;
1150
1198
  }
1151
1199
  }
1200
+
1201
+ // See https://datatracker.ietf.org/doc/html/rfc9485#name-ecmascript-regexps
1202
+ mapRegexp(pattern) {
1203
+ let escaped = false;
1204
+ let charClass = false;
1205
+ const parts = [];
1206
+ for (const ch of pattern) {
1207
+ switch (ch) {
1208
+ case ".":
1209
+ if (!escaped && !charClass) {
1210
+ parts.push("(?:(?![\r\n])\\P{Cs}|\\p{Cs}\\p{Cs})");
1211
+ } else {
1212
+ parts.push(ch);
1213
+ escaped = false;
1214
+ }
1215
+ break;
1216
+ case "\\":
1217
+ escaped = true;
1218
+ parts.push(ch);
1219
+ break;
1220
+ case "[":
1221
+ charClass = true;
1222
+ escaped = false;
1223
+ parts.push(ch);
1224
+ break;
1225
+ case "]":
1226
+ charClass = false;
1227
+ escaped = false;
1228
+ parts.push(ch);
1229
+ break;
1230
+ default:
1231
+ escaped = false;
1232
+ parts.push(ch);
1233
+ break;
1234
+ }
1235
+ }
1236
+ return parts.join("");
1237
+ }
1152
1238
  }
1153
1239
 
1154
1240
  class Value {
@@ -1167,7 +1253,8 @@ let TokenKind = /*#__PURE__*/function (TokenKind) {
1167
1253
  TokenKind["AND"] = "TOKEN_AND";
1168
1254
  TokenKind["COLON"] = "TOKEN_COLON";
1169
1255
  TokenKind["COMMA"] = "TOKEN_COMMA";
1170
- TokenKind["CURRENT"] = "TOKEN_CURRENT_NODE";
1256
+ TokenKind["CURRENT"] = "TOKEN_CURRENT_VALUE";
1257
+ TokenKind["CURRENT_KEY"] = "TOKEN_CURRENT_KEY";
1171
1258
  TokenKind["DDOT"] = "TOKEN_DDOT";
1172
1259
  TokenKind["DOT"] = "TOKEN_DOT";
1173
1260
  TokenKind["DOUBLE_QUOTE_STRING"] = "TOKEN_DOUBLE_QUOTE_STRING";
@@ -1181,7 +1268,10 @@ let TokenKind = /*#__PURE__*/function (TokenKind) {
1181
1268
  TokenKind["GT"] = "TOKEN_GT";
1182
1269
  TokenKind["INDEX"] = "TOKEN_INDEX";
1183
1270
  TokenKind["KEY"] = "TOKEN_KEY";
1271
+ TokenKind["KEY_DOUBLE_QUOTE_STRING"] = "TOKEN_KEY_DOUBLE_QUOTE_STRING";
1272
+ TokenKind["KEY_SINGLE_QUOTE_STRING"] = "TOKEN_KEY_SINGLE_QUOTE_STRING";
1184
1273
  TokenKind["KEYS"] = "TOKEN_KEYS";
1274
+ TokenKind["KEYS_FILTER"] = "TOKEN_KEYS_FILTER";
1185
1275
  TokenKind["LBRACKET"] = "TOKEN_LBRACKET";
1186
1276
  TokenKind["LE"] = "TOKEN_LE";
1187
1277
  TokenKind["LG"] = "TOKEN_LG";
@@ -1262,6 +1352,7 @@ const indexPattern = /-?\d+/y;
1262
1352
  const intPattern = /-?[0-9]+/y;
1263
1353
  const namePattern = /[\u0080-\uFFFFa-zA-Z_][\u0080-\uFFFFa-zA-Z0-9_-]*/y;
1264
1354
  const whitespace = new Set([" ", "\n", "\t", "\r"]);
1355
+ const nameFirstPattern = /[\u0080-\uFFFFa-zA-Z_]/; // don't set sticky bit
1265
1356
 
1266
1357
  /**
1267
1358
  * JSONPath lexical scanner.
@@ -1334,6 +1425,11 @@ class Lexer {
1334
1425
  if (ch) this.backup();
1335
1426
  return ch;
1336
1427
  }
1428
+ peekMatch(pattern) {
1429
+ const ch = this.next();
1430
+ if (ch) this.backup();
1431
+ return pattern.test(ch);
1432
+ }
1337
1433
  accept(valid) {
1338
1434
  const ch = this.next();
1339
1435
  if (valid.has(ch)) return true;
@@ -1463,10 +1559,28 @@ function lexDescendantSelection(l) {
1463
1559
  l.emit(TokenKind.NAME);
1464
1560
  return lexSegment;
1465
1561
  }
1466
- if (!l.environment.strict && l.acceptMatchRun(l.environment.keysPattern)) {
1467
- // Non-standard keys selector
1468
- l.emit(TokenKind.KEYS);
1469
- return lexSegment;
1562
+ if (!l.environment.strict) {
1563
+ // We're effectively disabling the _key selector_ and _keys filter selector_ if a
1564
+ // custom _keys selector_ is set.
1565
+ if (l.environment.keysPattern.source === "~" && l.peek() === "~") {
1566
+ l.next();
1567
+ if (l.peekMatch(nameFirstPattern)) {
1568
+ // Non-standard key selector
1569
+ l.ignore(); // ignore ~
1570
+ l.acceptMatchRun(namePattern);
1571
+ l.emit(TokenKind.KEY);
1572
+ return lexSegment;
1573
+ } else {
1574
+ // Non-standard keys selector
1575
+ l.emit(TokenKind.KEYS);
1576
+ return lexSegment;
1577
+ }
1578
+ } else if (l.acceptMatchRun(l.environment.keysPattern)) {
1579
+ // NOTE: A custom keys pattern does not play well with other non-standard key selectors.
1580
+ // We leave this here for backwards compatibility.
1581
+ l.emit(TokenKind.KEYS);
1582
+ return lexSegment;
1583
+ }
1470
1584
  }
1471
1585
  const ch = l.next();
1472
1586
  switch (ch) {
@@ -1491,9 +1605,28 @@ function lexDotSelector(l) {
1491
1605
  l.error("unexpected whitespace after dot");
1492
1606
  return null;
1493
1607
  }
1494
- if (!l.environment.strict && l.acceptMatchRun(l.environment.keysPattern)) {
1495
- l.emit(TokenKind.KEYS);
1496
- return lexSegment;
1608
+ if (!l.environment.strict) {
1609
+ // We're effectively disabling the _key selector_ and _keys filter selector_ if a
1610
+ // custom _keys selector_ is set.
1611
+ if (l.environment.keysPattern.source === "~" && l.peek() === "~") {
1612
+ l.next();
1613
+ if (l.peekMatch(nameFirstPattern)) {
1614
+ // Non-standard key selector
1615
+ l.ignore(); // ignore ~
1616
+ l.acceptMatchRun(namePattern);
1617
+ l.emit(TokenKind.KEY);
1618
+ return lexSegment;
1619
+ } else {
1620
+ // Non-standard keys selector
1621
+ l.emit(TokenKind.KEYS);
1622
+ return lexSegment;
1623
+ }
1624
+ } else if (l.acceptMatchRun(l.environment.keysPattern)) {
1625
+ // NOTE: A custom keys pattern does not play well with other non-standard key selectors.
1626
+ // We leave this here for backwards compatibility.
1627
+ l.emit(TokenKind.KEYS);
1628
+ return lexSegment;
1629
+ }
1497
1630
  }
1498
1631
  if (l.acceptMatchRun(namePattern)) {
1499
1632
  l.emit(TokenKind.NAME);
@@ -1516,8 +1649,25 @@ function lexInsideBracketedSelection(l) {
1516
1649
  continue;
1517
1650
  }
1518
1651
  if (!l.environment.strict && l.acceptMatchRun(l.environment.keysPattern)) {
1519
- l.emit(TokenKind.KEYS);
1520
- continue;
1652
+ // FIXME: fall back to legacy behavior if keysPattern is not the default
1653
+ switch (l.peek()) {
1654
+ case "'":
1655
+ l.ignore(); // ~
1656
+ l.next();
1657
+ return lexSingleQuoteKeyString(l);
1658
+ case '"':
1659
+ l.ignore(); // ~
1660
+ l.next();
1661
+ return lexDoubleQuoteKeyString(l);
1662
+ case "?":
1663
+ l.next();
1664
+ l.emit(TokenKind.KEYS_FILTER);
1665
+ l.filterLevel += 1;
1666
+ return lexInsideFilter;
1667
+ default:
1668
+ l.emit(TokenKind.KEYS);
1669
+ continue;
1670
+ }
1521
1671
  }
1522
1672
  const ch = l.next();
1523
1673
  switch (ch) {
@@ -1604,7 +1754,7 @@ function lexInsideFilter(l) {
1604
1754
  l.emit(TokenKind.CURRENT);
1605
1755
  return lexSegment;
1606
1756
  case "#":
1607
- l.emit(TokenKind.KEY);
1757
+ l.emit(TokenKind.CURRENT_KEY);
1608
1758
  return lexSegment;
1609
1759
  case ".":
1610
1760
  l.backup();
@@ -1704,8 +1854,7 @@ function lexInsideFilter(l) {
1704
1854
  * @param state - The state function to return control to.
1705
1855
  * @returns String tokenizing state function.
1706
1856
  */
1707
- function makeLexString(quote, state) {
1708
- // eslint-disable-next-line sonarjs/cognitive-complexity
1857
+ function makeLexString(quote, state, token_kind) {
1709
1858
  function _lexString(l) {
1710
1859
  l.ignore();
1711
1860
  if (l.peek() === quote) {
@@ -1731,7 +1880,7 @@ function makeLexString(quote, state) {
1731
1880
  }
1732
1881
  if (ch === quote) {
1733
1882
  l.backup();
1734
- l.emit(quote === "'" ? TokenKind.SINGLE_QUOTE_STRING : TokenKind.DOUBLE_QUOTE_STRING);
1883
+ l.emit(token_kind);
1735
1884
  l.next();
1736
1885
  l.ignore();
1737
1886
  return state;
@@ -1740,10 +1889,12 @@ function makeLexString(quote, state) {
1740
1889
  }
1741
1890
  return _lexString;
1742
1891
  }
1743
- const lexSingleQuoteStringInsideBracketSelection = makeLexString("'", lexInsideBracketedSelection);
1744
- const lexDoubleQuoteStringInsideBracketSelection = makeLexString('"', lexInsideBracketedSelection);
1745
- const lexSingleQuoteStringInsideFilterExpression = makeLexString("'", lexInsideFilter);
1746
- const lexDoubleQuoteStringInsideFilterExpression = makeLexString('"', lexInsideFilter);
1892
+ const lexSingleQuoteStringInsideBracketSelection = makeLexString("'", lexInsideBracketedSelection, TokenKind.SINGLE_QUOTE_STRING);
1893
+ const lexDoubleQuoteStringInsideBracketSelection = makeLexString('"', lexInsideBracketedSelection, TokenKind.DOUBLE_QUOTE_STRING);
1894
+ const lexSingleQuoteStringInsideFilterExpression = makeLexString("'", lexInsideFilter, TokenKind.SINGLE_QUOTE_STRING);
1895
+ const lexDoubleQuoteStringInsideFilterExpression = makeLexString('"', lexInsideFilter, TokenKind.DOUBLE_QUOTE_STRING);
1896
+ const lexSingleQuoteKeyString = makeLexString("'", lexInsideBracketedSelection, TokenKind.KEY_SINGLE_QUOTE_STRING);
1897
+ const lexDoubleQuoteKeyString = makeLexString('"', lexInsideBracketedSelection, TokenKind.KEY_DOUBLE_QUOTE_STRING);
1747
1898
 
1748
1899
  /**
1749
1900
  * Base class for all JSONPath segments and selectors.
@@ -2392,6 +2543,38 @@ class CurrentKey extends FilterExpression {
2392
2543
  }
2393
2544
  }
2394
2545
 
2546
+ class KeySelector extends JSONPathSelector {
2547
+ constructor(environment, token, key) {
2548
+ let shorthand = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
2549
+ super(environment, token);
2550
+ this.environment = environment;
2551
+ this.token = token;
2552
+ this.key = key;
2553
+ this.shorthand = shorthand;
2554
+ }
2555
+ resolve(nodes) {
2556
+ const rv = [];
2557
+ for (const node of nodes) {
2558
+ if (node.value instanceof String || isArray(node.value)) continue;
2559
+ if (isObject(node.value) && hasStringKey(node.value, this.key)) {
2560
+ rv.push(new JSONPathNode(this.key, node.location.concat(`${KEY_MARK}${this.key}`), node.root));
2561
+ }
2562
+ }
2563
+ return rv;
2564
+ }
2565
+ *lazyResolve(nodes) {
2566
+ for (const node of nodes) {
2567
+ if (node.value instanceof String || isArray(node.value)) continue;
2568
+ if (isObject(node.value) && hasStringKey(node.value, this.key)) {
2569
+ yield new JSONPathNode(this.key, node.location.concat(`${KEY_MARK}${this.key}`), node.root);
2570
+ }
2571
+ }
2572
+ }
2573
+ toString() {
2574
+ return this.shorthand ? `[~'${this.key.replaceAll("'", "\\'")}']` : `~'${this.key.replaceAll("'", "\\'")}'`;
2575
+ }
2576
+ }
2577
+
2395
2578
  /**
2396
2579
  * Object property name selector or array index selector.
2397
2580
  */
@@ -2408,10 +2591,8 @@ class KeysSelector extends JSONPathSelector {
2408
2591
  for (const node of nodes) {
2409
2592
  if (node.value instanceof String || isArray(node.value)) continue;
2410
2593
  if (isObject(node.value)) {
2411
- let i = 0;
2412
2594
  for (const [key, _] of this.environment.entries(node.value)) {
2413
- rv.push(new JSONPathNode(key, node.location.concat("[~]", `[${i}]`), node.root));
2414
- i++;
2595
+ rv.push(new JSONPathNode(key, node.location.concat(`${KEY_MARK}${key}`), node.root));
2415
2596
  }
2416
2597
  }
2417
2598
  }
@@ -2421,10 +2602,8 @@ class KeysSelector extends JSONPathSelector {
2421
2602
  for (const node of nodes) {
2422
2603
  if (node.value instanceof String || isArray(node.value)) continue;
2423
2604
  if (isObject(node.value)) {
2424
- let i = 0;
2425
2605
  for (const [key, _] of this.environment.entries(node.value)) {
2426
- yield new JSONPathNode(key, node.location.concat("[~]", `[${i}]`), node.root);
2427
- i++;
2606
+ yield new JSONPathNode(key, node.location.concat(`${KEY_MARK}${key}`), node.root);
2428
2607
  }
2429
2608
  }
2430
2609
  }
@@ -2433,6 +2612,56 @@ class KeysSelector extends JSONPathSelector {
2433
2612
  return this.shorthand ? "[~]" : "~";
2434
2613
  }
2435
2614
  }
2615
+ class KeysFilterSelector extends JSONPathSelector {
2616
+ constructor(environment, token, expression) {
2617
+ super(environment, token);
2618
+ this.environment = environment;
2619
+ this.token = token;
2620
+ this.expression = expression;
2621
+ }
2622
+ resolve(nodes) {
2623
+ const rv = [];
2624
+ for (const node of nodes) {
2625
+ if (node.value instanceof String || isArray(node.value)) continue;
2626
+ if (isObject(node.value)) {
2627
+ for (const [key, value] of this.environment.entries(node.value)) {
2628
+ const filterContext = {
2629
+ environment: this.environment,
2630
+ currentValue: value,
2631
+ rootValue: node.root,
2632
+ currentKey: key
2633
+ };
2634
+ if (this.expression.evaluate(filterContext)) {
2635
+ rv.push(new JSONPathNode(key, node.location.concat(`${KEY_MARK}${key}`), node.root));
2636
+ }
2637
+ }
2638
+ }
2639
+ }
2640
+ return rv;
2641
+ }
2642
+ *lazyResolve(nodes) {
2643
+ for (const node of nodes) {
2644
+ if (node.value instanceof String || isArray(node.value)) continue;
2645
+ if (isObject(node.value)) {
2646
+ for (const [key, value] of this.environment.entries(node.value)) {
2647
+ const filterContext = {
2648
+ environment: this.environment,
2649
+ currentValue: value,
2650
+ rootValue: node.root,
2651
+ lazy: true,
2652
+ currentKey: key
2653
+ };
2654
+ if (this.expression.evaluate(filterContext)) {
2655
+ yield new JSONPathNode(key, node.location.concat(`${KEY_MARK}${key}`), node.root);
2656
+ }
2657
+ }
2658
+ }
2659
+ }
2660
+ }
2661
+ toString() {
2662
+ return `~?${this.expression.toString()}`;
2663
+ }
2664
+ }
2436
2665
 
2437
2666
  const PRECEDENCE_LOWEST = 1;
2438
2667
  const PRECEDENCE_LOGICAL_OR = 4;
@@ -2449,7 +2678,7 @@ const COMPARISON_OPERATORS = new Set(["==", ">=", ">", "<=", "<", "!="]);
2449
2678
  class Parser {
2450
2679
  constructor(environment) {
2451
2680
  this.environment = environment;
2452
- 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], [TokenKind.KEY, this.parseCurrentKey], [TokenKind.KEY, this.parseCurrentKey]]);
2681
+ 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], [TokenKind.CURRENT_KEY, this.parseCurrentKey]]);
2453
2682
  }
2454
2683
  parse(stream) {
2455
2684
  if (stream.current.kind === TokenKind.ROOT) stream.next();
@@ -2481,6 +2710,8 @@ class Parser {
2481
2710
  return new NameSelector(this.environment, stream.current, stream.current.value, true);
2482
2711
  case TokenKind.WILD:
2483
2712
  return new WildcardSelector(this.environment, stream.current, true);
2713
+ case TokenKind.KEY:
2714
+ return new KeySelector(this.environment, stream.current, stream.current.value, true);
2484
2715
  case TokenKind.KEYS:
2485
2716
  return new KeysSelector(this.environment, stream.current, true);
2486
2717
  case TokenKind.DDOT:
@@ -2576,6 +2807,13 @@ class Parser {
2576
2807
  case TokenKind.WILD:
2577
2808
  items.push(new WildcardSelector(this.environment, stream.current));
2578
2809
  break;
2810
+ case TokenKind.KEY_SINGLE_QUOTE_STRING:
2811
+ case TokenKind.KEY_DOUBLE_QUOTE_STRING:
2812
+ items.push(new KeySelector(this.environment, stream.current, this.decodeString(stream.current, true), false));
2813
+ break;
2814
+ case TokenKind.KEYS_FILTER:
2815
+ items.push(this.parseFilter(stream, true));
2816
+ break;
2579
2817
  case TokenKind.KEYS:
2580
2818
  items.push(new KeysSelector(this.environment, stream.current));
2581
2819
  break;
@@ -2596,6 +2834,7 @@ class Parser {
2596
2834
  return new BracketedSelection(this.environment, token, items);
2597
2835
  }
2598
2836
  parseFilter(stream) {
2837
+ let keys = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
2599
2838
  const tok = stream.next();
2600
2839
  const expr = this.parseFilterExpression(stream);
2601
2840
  if (expr instanceof FunctionExtension) {
@@ -2604,7 +2843,7 @@ class Parser {
2604
2843
  throw new JSONPathTypeError(`result of ${expr.name}() must be compared`, expr.token);
2605
2844
  }
2606
2845
  }
2607
- return new FilterSelector(this.environment, tok, new LogicalExpression(tok, expr));
2846
+ return keys ? new KeysFilterSelector(this.environment, tok, new LogicalExpression(tok, expr)) : new FilterSelector(this.environment, tok, new LogicalExpression(tok, expr));
2608
2847
  }
2609
2848
  parseBoolean(stream) {
2610
2849
  if (stream.current.kind === TokenKind.FALSE) return new BooleanLiteral(stream.current, false);
@@ -3032,6 +3271,7 @@ var index$1 = /*#__PURE__*/Object.freeze({
3032
3271
  JSONPathRecursionLimitError: JSONPathRecursionLimitError,
3033
3272
  JSONPathSyntaxError: JSONPathSyntaxError,
3034
3273
  JSONPathTypeError: JSONPathTypeError,
3274
+ KEY_MARK: KEY_MARK,
3035
3275
  Nothing: Nothing,
3036
3276
  Token: Token,
3037
3277
  TokenKind: TokenKind,
@@ -3524,6 +3764,6 @@ var index = /*#__PURE__*/Object.freeze({
3524
3764
  apply: apply
3525
3765
  });
3526
3766
 
3527
- const version = "1.2.1";
3767
+ const version = "1.3.1";
3528
3768
 
3529
3769
  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, lazyQuery, query, resolve, version };