Skip to content

Commit 72dc3b4

Browse files
committed
feat: allow arg to other type callbacks
Also: - feat: add @symbol, @promise, @jsonReference other types - feat: add separate optional imports for structuredCloning and extra javascript types
1 parent ed83f1f commit 72dc3b4

23 files changed

Lines changed: 1526 additions & 177 deletions

‎CHANGES.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# CHANGES for jsonpath-plus
22

3+
## 11.2.0
4+
5+
- feat: allow arg to other type callbacks
6+
- feat: add @symbol, @Promise, @jsonReference other types
7+
- feat: add separate optional imports for structuredCloning and extra javascript types
8+
39
## 11.1.1
410

511
- fix: avoid treating at sign within regex as current context; fixes #280

‎README.md‎

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,29 @@ evaluate method (as the first argument) include:
215215
This allows creating custom type operators that can be used in queries
216216
(e.g., `@myType()`). The function will be invoked with the value of the item,
217217
its path, its parent, and its parent's property name. It should return a
218-
boolean indicating whether the supplied value matches the custom type.
218+
boolean indicating whether the supplied value matches the custom type. May
219+
also be set to:
220+
1. The value of the import `jsonpath-plus/src/structuredCloningTypes.js` which predefines the following types:
221+
@NaN(), @BigIntObject(), @Date(), @Set(), @Map(), @RegExp(),
222+
@Blob(optionalMimeType), @File(optionalMimeType), @Boolean(), @Number(),
223+
@String(), @Infinity(), @negativeInfinity(), @negativeZero(),
224+
@DOMException(), @Error(), @EvalError(), @RangeError(), @ReferenceError(),
225+
@SyntaxError(), @TypeError(), @URIError(), @AggregateError(),
226+
@QuotaExceededError(), @WebTransportError(), @FileList, @ArrayBuffer(),
227+
@DataView(), @ImageData(), @ImageBitmap(), @Int8Array(), @Uint8Array(),
228+
@Uint8ClampedArray(), @Int16Array(), @Uint16Array(), @Int32Array(),
229+
@Uint32Array(), @Float32Array(), @Float64Array(), @BigInt64Array(),
230+
@BigUint64Array(), @Float16Array, @DOMMatrix(), @DOMMatrixReadOnly(),
231+
@DOMPoint(), @DOMPointReadOnly(), @DOMRect, @DOMRectReadOnly(),
232+
@DOMQuad(), @CryptoKey(), @AudioData(), @EncodedAudioChunk(),
233+
@EncodedVideoChunk(), @VideoFrame(), @CropTarget,
234+
@FileSystemDirectoryHandle(), @FileSystemFileHandle(),
235+
@GPUCompilationInfo(), @GPUCompilationMessage(),
236+
@GPUPipelineError(), @RTCCertificate(), @RTCEncodedAudioFrame(),
237+
@RTCEncodedVideoFrame()
238+
2. The value of the import `jsonpath-plus/src/javascriptTypes.js` which predefines the following types:
239+
@Generator(), @AsyncGenerator(), @GeneratorFunction(),
240+
@AsyncGeneratorFunction(), @Iterable(), @AsyncIterable(), @Iterator()
219241

220242
### Instance methods
221243

@@ -360,7 +382,7 @@ may be helpful (set to `xml` or `xml-strict`).
360382
| `/store/*/*[name(parent::*) != 'book']` | `$.store.*[?(@parentProperty !== "book")]` | Grabs the grandchildren of store whose parent property is not book (i.e., bicycle's children, "color" and "price") | `@parentProperty` is not present in the original spec |
361383
| `//book[count(preceding-sibling::*) != 0]/*/text()` | `$..book.*[?(@parentProperty !== 0)]` | Get the property values of all book instances whereby the parent property of these values (i.e., the array index holding the book item parent object) is not 0 | `@parentProperty` is not present in the original spec |
362384
| `//book[price = /store/book[3]/price]` | `$..book[?(@.price === @root.store.book[2].price)]` | Filter all books whose price equals the price of the third book | `@root` is not present in the original spec |
363-
| `//book/../*[. instance of element(*, xs:decimal)]` (in XPath 2.0) | `$..book..*@number()` | Get the numeric values within the book array | `@number()`, the other basic types (`@boolean()`, `@string()`), other low-level derived types (`@null()`, `@object()`, `@array()`), the JSONSchema-added type, `@integer()`, the compound type `@scalar()` (which also accepts `undefined` and non-finite numbers for JavaScript objects as well as all of the basic non-object/non-function types), the type, `@other()`, to be used in conjunction with a user-defined callback (see `otherTypeCallback`) and the following non-JSON types that can nevertheless be used with JSONPath when querying non-JSON JavaScript objects (`@undefined()`, `@function()`, `@nonFinite()`) are not present in the original spec |
385+
| `//book/../*[. instance of element(*, xs:decimal)]` (in XPath 2.0) | `$..book..*@number()` | Get the numeric values within the book array | `@number()`, the other basic types (`@boolean()`, `@string()`), other low-level derived types (`@null()`, `@object()`, `@array()`), the JSONSchema-added type, `@integer()`, the compound type `@scalar()` (which also accepts `undefined` and non-finite numbers for JavaScript objects as well as all of the basic non-object/non-function types), the type, `@other()`, to be used in conjunction with a user-defined callback (see `otherTypeCallback`). The special JSON type `@jsonReference()` is also provided, and the following non-JSON types can nevertheless also be used with JSONPath when querying non-JSON JavaScript objects (`@undefined()`, `@function()`, `@nonFinite()`, `@symbol()`, `@BigInt()`, and `@Promise`) are not present in the original spec |
364386
| `//book/*[name() = 'category' and matches(., 'tion$')]` (XPath 2.0) | `$..book.*[?(@property === "category" && @.match(/TION$/i))]` | All categories of books which match the regex (end in 'TION' case insensitive) | `@property` is not present in the original spec. |
365387
| `//book/*[matches(name(), 'bn$')]/parent::*` (XPath 2.0) | `$..book.*[?(@property.match(/bn$/i))]^` | All books which have a property matching the regex (end in 'TION' case insensitive) | `@property` is not present in the original spec. Note: Uses the parent selector `^` at the end of the expression to return to the parent object; without the parent selector, it matches the two `isbn` key values. |
366388
| | `` ` `` (e.g., `` `$`` to match a property literally named `$`) | Escapes the entire sequence following (to be treated as a literal) | `` ` `` is not present in the original spec; to get a literal backtick, use an additional backtick to escape |

‎badges/tests-badge.svg‎

Lines changed: 1 addition & 1 deletion
Loading

‎dist/index-browser-esm.js‎

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,6 +1508,13 @@ class SafeScript {
15081508
}
15091509
}
15101510

1511+
/**
1512+
* @param {unknown} val
1513+
*/
1514+
const toStringTag = val => {
1515+
return Object.prototype.toString.call(val).slice(8, -1);
1516+
};
1517+
15111518
/* eslint-disable camelcase -- Convenient for escaping */
15121519
/* eslint-disable class-methods-use-this -- Consistent monkey-patching */
15131520
/* eslint-disable unicorn/prefer-private-class-fields -- Allow
@@ -1534,7 +1541,8 @@ const pathCache = new Map();
15341541
/**
15351542
* @typedef {"scalar"|"boolean"|"string"|"undefined"
15361543
* |"function"|"integer"|"number"|"nonFinite"|"object"
1537-
* |"array"|"other"|"null"} ValueType
1544+
* |"array"|"other"|"null"|"symbol"|"Promise"|"BigInt"
1545+
* |"jsonReference"} ValueType
15381546
*/
15391547

15401548
/**
@@ -1604,6 +1612,7 @@ function unshift(item, arr) {
16041612
* @param {ExpressionArray} path
16051613
* @param {ParentValue} parent
16061614
* @param {string|number|null} parentPropName
1615+
* @param {string} arg
16071616
* @returns {boolean|null}
16081617
*/
16091618

@@ -1679,8 +1688,8 @@ function unshift(item, arr) {
16791688
* @property {JSONPathCallback} [callback]
16801689
* @property {OtherTypeCallback} [otherTypeCallback] Defaults to
16811690
* function which throws on encountering `@other`
1682-
* @property {Record<string, OtherTypeCallback>} [customTypes] Map of custom
1683-
* type operator names to their evaluation callbacks
1691+
* @property {Record<string, OtherTypeCallback>} [customTypes] Map of
1692+
* custom type operator names to their evaluation callbacks
16841693
* @property {boolean} [autostart=true]
16851694
* @property {boolean} [ignoreEvalErrors=false]
16861695
*/
@@ -2173,58 +2182,59 @@ class JSONPathClass {
21732182
addRet(this._trace(unshift(exprToUse, x), val, path, parent, parentPropName, callback, hasArrExpr));
21742183
} else if (loc[0] === '@') {
21752184
// value type: @boolean(), etc.
2185+
// eslint-disable-next-line no-useless-assignment -- ESLint bug
21762186
let addType = false;
2177-
const valueType = /** @type {ValueType|string} */loc.slice(1, -2);
2187+
const parenthIndex = loc.indexOf('(');
2188+
const valueType = /** @type {ValueType|string} */loc.slice(1, parenthIndex);
2189+
const lastParenth = loc.lastIndexOf(')');
2190+
const argRaw = loc.slice(parenthIndex + 1, lastParenth);
2191+
const arg = argRaw ? JSON.parse(argRaw) : undefined;
21782192
switch (valueType) {
21792193
case 'scalar':
2180-
if (!val || !['object', 'function'].includes(typeof val)) {
2181-
addType = true;
2182-
}
2194+
addType = !val || !['object', 'function'].includes(typeof val);
21832195
break;
21842196
case 'boolean':
21852197
case 'string':
21862198
case 'undefined':
21872199
case 'function':
2188-
if (typeof val === valueType) {
2189-
addType = true;
2190-
}
2200+
addType = typeof val === valueType;
21912201
break;
21922202
case 'integer':
2193-
if (Number.isFinite(val) && !(/** @type {number} */val % 1)) {
2194-
addType = true;
2195-
}
2203+
addType = Number.isFinite(val) && !(/** @type {number} */val % 1);
21962204
break;
21972205
case 'number':
2198-
if (Number.isFinite(val)) {
2199-
addType = true;
2200-
}
2206+
addType = Number.isFinite(val);
22012207
break;
22022208
case 'nonFinite':
2203-
if (typeof val === 'number' && !Number.isFinite(val)) {
2204-
addType = true;
2205-
}
2209+
addType = typeof val === 'number' && !Number.isFinite(val);
22062210
break;
22072211
case 'object':
2208-
if (val && typeof val === valueType) {
2209-
addType = true;
2210-
}
2212+
addType = Boolean(val) && typeof val === valueType;
22112213
break;
22122214
case 'array':
2213-
if (Array.isArray(val)) {
2214-
addType = true;
2215-
}
2215+
addType = Array.isArray(val);
22162216
break;
22172217
case 'other':
2218-
addType = /** @type {OtherTypeCallback} */this.currOtherTypeCallback(val, path, parent, parentPropName) || false;
2218+
addType = /** @type {OtherTypeCallback} */this.currOtherTypeCallback(val, path, parent, parentPropName, arg) || false;
22192219
break;
22202220
case 'null':
2221-
if (val === null) {
2222-
addType = true;
2223-
}
2221+
addType = val === null;
2222+
break;
2223+
case 'symbol':
2224+
addType = typeof val === 'symbol';
2225+
break;
2226+
case 'BigInt':
2227+
addType = typeof val === 'bigint';
2228+
break;
2229+
case 'Promise':
2230+
addType = toStringTag(val) === 'Promise';
2231+
break;
2232+
case 'jsonReference':
2233+
addType = Boolean(val) && typeof val === 'object' && Object.hasOwn(/** @type {object} */val, '$ref');
22242234
break;
22252235
default:
22262236
if (this.currCustomTypes && Object.hasOwn(this.currCustomTypes, valueType)) {
2227-
addType = this.currCustomTypes[valueType](val, path, parent, parentPropName) || false;
2237+
addType = this.currCustomTypes[valueType](val, path, parent, parentPropName, arg) || false;
22282238
} else {
22292239
throw new TypeError('Unknown value type ' + valueType);
22302240
}
@@ -2385,7 +2395,7 @@ class JSONPathClass {
23852395
// character) while leaving quoted string literals and
23862396
// regex literals (a `/` where an operand is expected,
23872397
// as opposed to division) intact
2388-
.replaceAll(/('(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*"|(?:^|[\(,=:\[!\|?\{\};+\-*%<>~^]|&)\s*\/(?:\\.|\[(?:\\.|[^\]\\])*\]|[^\/\\\[])+\/[dgimsuvy]*)|@(?![\w$])/gv, (_, literal) => literal ?? '_$_v');
2398+
.replaceAll(/('(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*"|(?<=(?:^|[\(,=:\[!\|?\{\};+\-*%<>~^]|&)\s*)\/(?:\\.|\[(?:\\.|[^\]\\])*\]|[^\/\\\[])+\/[dgimsuvy]*)|@(?![\w$])/gv, (_, literal) => literal ?? '_$_v');
23892399
if (containsPath) {
23902400
script = script.replaceAll('@path', '_$_path');
23912401
}
@@ -2516,7 +2526,7 @@ JSONPath.toPathArray = function (expr) {
25162526
const subx = [];
25172527
const normalized = expr
25182528
// Properties
2519-
.replaceAll(/@[\w$\-]+\(\)/gv, ';$&;')
2529+
.replaceAll(/@[\w$\-]+\([^\)]*\)/gv, ';$&;')
25202530
// Parenthetical evaluations (filtering and otherwise), directly
25212531
// within brackets or single quotes
25222532
.replaceAll(/[\['](\??\(.*?\))[\]'](?!.\])/gv, function ($0, $1) {

‎dist/index-browser-esm.min.js‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/index-browser-esm.min.js.map‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)