Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ that usage and call JSONPath.clearCache() when cache invalidation is needed.

Other changes:

- feat: add `customTypes` option for providing own other type callbacks (e.g., `@blob()`) (@brettz9)
- fix(slice): explicit zero end no longer returns the whole array (#265) (@spokodev)
- fix: separate JSONPath path and script caches
- fix: indicate that the `OtherTypeCallback` callback type can accept a `parentPropName` with type `number` (@brettz9)
- fix: separate JSONPath path and script caches (@brettz9)
- fix: restore `JSONPath.prototype.evaluate`, `safeVm`, and `vm` compatibility
- fix(safe-eval): harden operator lookup against prototype inheritance (@brettz9)
- refactor: expose JSONPathClass prototype through JSONPath for compatibility
- docs: security notes
- test(safe-eval): guard bind() escape route for constructor access (@brettz9)
- test: restore full test coverage (@brettz9)
- chore: pnpm update (@brettz9)
- refactor: implement TypeScript-as-JSDoc and auto-build declaration files from this (avoiding need for maintaining declaration file manually)
- chore: update devDeps
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ evaluate method (as the first argument) include:
and it should return a boolean indicating whether the supplied value
belongs to the "other" type or not (or it may handle transformations and
return false).
- ***customTypes*** (**default: {}**) - A key-value map of type names to functions.
This allows creating custom type operators that can be used in queries
(e.g., `@myType()`). The function will be invoked with the value of the item,
its path, its parent, and its parent's property name. It should return a
boolean indicating whether the supplied value matches the custom type.

### Instance methods

Expand Down
2 changes: 1 addition & 1 deletion badges/coverage-badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion badges/tests-badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 17 additions & 5 deletions dist/index-browser-esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -1591,7 +1591,7 @@ function unshift(item, arr) {
* @param {unknown} val
* @param {ExpressionArray} path
* @param {ParentValue} parent
* @param {string|null} parentPropName
* @param {string|number|null} parentPropName
* @returns {boolean|null}
*/

Expand Down Expand Up @@ -1667,6 +1667,8 @@ function unshift(item, arr) {
* @property {JSONPathCallback} [callback]
* @property {OtherTypeCallback} [otherTypeCallback] Defaults to
* function which throws on encountering `@other`
* @property {Record<string, OtherTypeCallback>} [customTypes] Map of custom
* type operator names to their evaluation callbacks
* @property {boolean} [autostart=true]
* @property {boolean} [ignoreEvalErrors=false]
*/
Expand Down Expand Up @@ -1795,6 +1797,9 @@ class JSONPathClass {
/** @type {OtherTypeCallback|undefined} */
this.currOtherTypeCallback = undefined;

/** @type {Record<string, OtherTypeCallback>|undefined} */
this.currCustomTypes = undefined;

/** @type {SandboxType|undefined} */
this.currSandbox = undefined;
this._hasParentSelector = false;
Expand All @@ -1813,6 +1818,7 @@ class JSONPathClass {
this.otherTypeCallback = opts.otherTypeCallback || otherTypeCallback || function () {
throw new TypeError('You must supply an otherTypeCallback callback option ' + 'with the @other() operator.');
};
this.customTypes = opts.customTypes || {};
if (opts.autostart !== false) {
const args = /** @type {JSONPathOptions} */{
path: optObj ? opts.path : expr
Expand Down Expand Up @@ -1873,6 +1879,7 @@ class JSONPathClass {
this.currSandbox = this.sandbox;
callback ||= this.callback;
this.currOtherTypeCallback = otherTypeCallback || this.otherTypeCallback;
this.currCustomTypes = this.customTypes;
if (expr && typeof expr === 'object' && !Array.isArray(expr)) {
const exprObj = expr;
if (!exprObj.path && exprObj.path !== '') {
Expand All @@ -1891,6 +1898,7 @@ class JSONPathClass {
this.currEval = Object.hasOwn(exprObj, 'eval') ? exprObj.eval : this.currEval;
callback = Object.hasOwn(exprObj, 'callback') ? exprObj.callback : callback;
this.currOtherTypeCallback = Object.hasOwn(exprObj, 'otherTypeCallback') ? exprObj.otherTypeCallback : this.currOtherTypeCallback;
this.currCustomTypes = Object.hasOwn(exprObj, 'customTypes') ? exprObj.customTypes : this.currCustomTypes;
currParent = Object.hasOwn(exprObj, 'parent') ? exprObj.parent : currParent;
currParentProperty = Object.hasOwn(exprObj, 'parentProperty') ? exprObj.parentProperty : currParentProperty;
expr = exprObj.path;
Expand Down Expand Up @@ -2151,7 +2159,7 @@ class JSONPathClass {
} else if (loc[0] === '@') {
// value type: @boolean(), etc.
let addType = false;
const valueType = /** @type {ValueType} */loc.slice(1, -2);
const valueType = /** @type {ValueType|string} */loc.slice(1, -2);
switch (valueType) {
case 'scalar':
if (!val || !['object', 'function'].includes(typeof val)) {
Expand Down Expand Up @@ -2192,7 +2200,7 @@ class JSONPathClass {
}
break;
case 'other':
addType = this.currOtherTypeCallback?.(val, path, parent, /** @type {string|null} */parentPropName) ?? false;
addType = /** @type {OtherTypeCallback} */this.currOtherTypeCallback(val, path, parent, parentPropName) || false;
break;
case 'null':
if (val === null) {
Expand All @@ -2201,7 +2209,11 @@ class JSONPathClass {
break;
/* c8 ignore next 2 */
default:
throw new TypeError('Unknown value type ' + valueType);
if (this.currCustomTypes && Object.hasOwn(this.currCustomTypes, valueType)) {
addType = this.currCustomTypes[valueType](val, path, parent, parentPropName) || false;
} else {
throw new TypeError('Unknown value type ' + valueType);
}
}
if (addType) {
retObj = {
Expand Down Expand Up @@ -2473,7 +2485,7 @@ JSONPath.toPathArray = function (expr) {
const subx = [];
const normalized = expr
// Properties
.replaceAll(/@(?:null|boolean|number|string|integer|undefined|nonFinite|scalar|array|object|function|other)\(\)/gu, ';$&;')
.replaceAll(/@[\w$-]+\(\)/gu, ';$&;')
// Parenthetical evaluations (filtering and otherwise), directly
// within brackets or single quotes
.replaceAll(/[['](\??\(.*?\))[\]'](?!.\])/gu, function ($0, $1) {
Expand Down
2 changes: 1 addition & 1 deletion dist/index-browser-esm.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index-browser-esm.min.js.map

Large diffs are not rendered by default.

22 changes: 17 additions & 5 deletions dist/index-browser-umd.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -1597,7 +1597,7 @@
* @param {unknown} val
* @param {ExpressionArray} path
* @param {ParentValue} parent
* @param {string|null} parentPropName
* @param {string|number|null} parentPropName
* @returns {boolean|null}
*/

Expand Down Expand Up @@ -1673,6 +1673,8 @@
* @property {JSONPathCallback} [callback]
* @property {OtherTypeCallback} [otherTypeCallback] Defaults to
* function which throws on encountering `@other`
* @property {Record<string, OtherTypeCallback>} [customTypes] Map of custom
* type operator names to their evaluation callbacks
* @property {boolean} [autostart=true]
* @property {boolean} [ignoreEvalErrors=false]
*/
Expand Down Expand Up @@ -1801,6 +1803,9 @@
/** @type {OtherTypeCallback|undefined} */
this.currOtherTypeCallback = undefined;

/** @type {Record<string, OtherTypeCallback>|undefined} */
this.currCustomTypes = undefined;

/** @type {SandboxType|undefined} */
this.currSandbox = undefined;
this._hasParentSelector = false;
Expand All @@ -1819,6 +1824,7 @@
this.otherTypeCallback = opts.otherTypeCallback || otherTypeCallback || function () {
throw new TypeError('You must supply an otherTypeCallback callback option ' + 'with the @other() operator.');
};
this.customTypes = opts.customTypes || {};
if (opts.autostart !== false) {
const args = /** @type {JSONPathOptions} */{
path: optObj ? opts.path : expr
Expand Down Expand Up @@ -1879,6 +1885,7 @@
this.currSandbox = this.sandbox;
callback ||= this.callback;
this.currOtherTypeCallback = otherTypeCallback || this.otherTypeCallback;
this.currCustomTypes = this.customTypes;
if (expr && typeof expr === 'object' && !Array.isArray(expr)) {
const exprObj = expr;
if (!exprObj.path && exprObj.path !== '') {
Expand All @@ -1897,6 +1904,7 @@
this.currEval = Object.hasOwn(exprObj, 'eval') ? exprObj.eval : this.currEval;
callback = Object.hasOwn(exprObj, 'callback') ? exprObj.callback : callback;
this.currOtherTypeCallback = Object.hasOwn(exprObj, 'otherTypeCallback') ? exprObj.otherTypeCallback : this.currOtherTypeCallback;
this.currCustomTypes = Object.hasOwn(exprObj, 'customTypes') ? exprObj.customTypes : this.currCustomTypes;
currParent = Object.hasOwn(exprObj, 'parent') ? exprObj.parent : currParent;
currParentProperty = Object.hasOwn(exprObj, 'parentProperty') ? exprObj.parentProperty : currParentProperty;
expr = exprObj.path;
Expand Down Expand Up @@ -2157,7 +2165,7 @@
} else if (loc[0] === '@') {
// value type: @boolean(), etc.
let addType = false;
const valueType = /** @type {ValueType} */loc.slice(1, -2);
const valueType = /** @type {ValueType|string} */loc.slice(1, -2);
switch (valueType) {
case 'scalar':
if (!val || !['object', 'function'].includes(typeof val)) {
Expand Down Expand Up @@ -2198,7 +2206,7 @@
}
break;
case 'other':
addType = this.currOtherTypeCallback?.(val, path, parent, /** @type {string|null} */parentPropName) ?? false;
addType = /** @type {OtherTypeCallback} */this.currOtherTypeCallback(val, path, parent, parentPropName) || false;
break;
case 'null':
if (val === null) {
Expand All @@ -2207,7 +2215,11 @@
break;
/* c8 ignore next 2 */
default:
throw new TypeError('Unknown value type ' + valueType);
if (this.currCustomTypes && Object.hasOwn(this.currCustomTypes, valueType)) {
addType = this.currCustomTypes[valueType](val, path, parent, parentPropName) || false;
} else {
throw new TypeError('Unknown value type ' + valueType);
}
}
if (addType) {
retObj = {
Expand Down Expand Up @@ -2479,7 +2491,7 @@
const subx = [];
const normalized = expr
// Properties
.replaceAll(/@(?:null|boolean|number|string|integer|undefined|nonFinite|scalar|array|object|function|other)\(\)/gu, ';$&;')
.replaceAll(/@[\w$-]+\(\)/gu, ';$&;')
// Parenthetical evaluations (filtering and otherwise), directly
// within brackets or single quotes
.replaceAll(/[['](\??\(.*?\))[\]'](?!.\])/gu, function ($0, $1) {
Expand Down
2 changes: 1 addition & 1 deletion dist/index-browser-umd.min.cjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index-browser-umd.min.cjs.map

Large diffs are not rendered by default.

22 changes: 17 additions & 5 deletions dist/index-node-cjs.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -1595,7 +1595,7 @@ function unshift(item, arr) {
* @param {unknown} val
* @param {ExpressionArray} path
* @param {ParentValue} parent
* @param {string|null} parentPropName
* @param {string|number|null} parentPropName
* @returns {boolean|null}
*/

Expand Down Expand Up @@ -1671,6 +1671,8 @@ function unshift(item, arr) {
* @property {JSONPathCallback} [callback]
* @property {OtherTypeCallback} [otherTypeCallback] Defaults to
* function which throws on encountering `@other`
* @property {Record<string, OtherTypeCallback>} [customTypes] Map of custom
* type operator names to their evaluation callbacks
* @property {boolean} [autostart=true]
* @property {boolean} [ignoreEvalErrors=false]
*/
Expand Down Expand Up @@ -1799,6 +1801,9 @@ class JSONPathClass {
/** @type {OtherTypeCallback|undefined} */
this.currOtherTypeCallback = undefined;

/** @type {Record<string, OtherTypeCallback>|undefined} */
this.currCustomTypes = undefined;

/** @type {SandboxType|undefined} */
this.currSandbox = undefined;
this._hasParentSelector = false;
Expand All @@ -1817,6 +1822,7 @@ class JSONPathClass {
this.otherTypeCallback = opts.otherTypeCallback || otherTypeCallback || function () {
throw new TypeError('You must supply an otherTypeCallback callback option ' + 'with the @other() operator.');
};
this.customTypes = opts.customTypes || {};
if (opts.autostart !== false) {
const args = /** @type {JSONPathOptions} */{
path: optObj ? opts.path : expr
Expand Down Expand Up @@ -1877,6 +1883,7 @@ class JSONPathClass {
this.currSandbox = this.sandbox;
callback ||= this.callback;
this.currOtherTypeCallback = otherTypeCallback || this.otherTypeCallback;
this.currCustomTypes = this.customTypes;
if (expr && typeof expr === 'object' && !Array.isArray(expr)) {
const exprObj = expr;
if (!exprObj.path && exprObj.path !== '') {
Expand All @@ -1895,6 +1902,7 @@ class JSONPathClass {
this.currEval = Object.hasOwn(exprObj, 'eval') ? exprObj.eval : this.currEval;
callback = Object.hasOwn(exprObj, 'callback') ? exprObj.callback : callback;
this.currOtherTypeCallback = Object.hasOwn(exprObj, 'otherTypeCallback') ? exprObj.otherTypeCallback : this.currOtherTypeCallback;
this.currCustomTypes = Object.hasOwn(exprObj, 'customTypes') ? exprObj.customTypes : this.currCustomTypes;
currParent = Object.hasOwn(exprObj, 'parent') ? exprObj.parent : currParent;
currParentProperty = Object.hasOwn(exprObj, 'parentProperty') ? exprObj.parentProperty : currParentProperty;
expr = exprObj.path;
Expand Down Expand Up @@ -2155,7 +2163,7 @@ class JSONPathClass {
} else if (loc[0] === '@') {
// value type: @boolean(), etc.
let addType = false;
const valueType = /** @type {ValueType} */loc.slice(1, -2);
const valueType = /** @type {ValueType|string} */loc.slice(1, -2);
switch (valueType) {
case 'scalar':
if (!val || !['object', 'function'].includes(typeof val)) {
Expand Down Expand Up @@ -2196,7 +2204,7 @@ class JSONPathClass {
}
break;
case 'other':
addType = this.currOtherTypeCallback?.(val, path, parent, /** @type {string|null} */parentPropName) ?? false;
addType = /** @type {OtherTypeCallback} */this.currOtherTypeCallback(val, path, parent, parentPropName) || false;
break;
case 'null':
if (val === null) {
Expand All @@ -2205,7 +2213,11 @@ class JSONPathClass {
break;
/* c8 ignore next 2 */
default:
throw new TypeError('Unknown value type ' + valueType);
if (this.currCustomTypes && Object.hasOwn(this.currCustomTypes, valueType)) {
addType = this.currCustomTypes[valueType](val, path, parent, parentPropName) || false;
} else {
throw new TypeError('Unknown value type ' + valueType);
}
}
if (addType) {
retObj = {
Expand Down Expand Up @@ -2477,7 +2489,7 @@ JSONPath.toPathArray = function (expr) {
const subx = [];
const normalized = expr
// Properties
.replaceAll(/@(?:null|boolean|number|string|integer|undefined|nonFinite|scalar|array|object|function|other)\(\)/gu, ';$&;')
.replaceAll(/@[\w$-]+\(\)/gu, ';$&;')
// Parenthetical evaluations (filtering and otherwise), directly
// within brackets or single quotes
.replaceAll(/[['](\??\(.*?\))[\]'](?!.\])/gu, function ($0, $1) {
Expand Down
Loading