Skip to content

Commit 258832a

Browse files
author
Jacob Roschen
committed
perf: improve evaluation speed of conditional queries
Use NodeJS' vm.Script class to cache the script that the context needs to be evaluated against. This provides large speed improvements (~50%) when a path contains conditional or JS logic.
1 parent 5d740b3 commit 258832a

File tree

3 files changed

+41
-25
lines changed

3 files changed

+41
-25
lines changed

src/jsonpath-browser.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,24 @@ const moveToAnotherArray = function (source, target, conditionCb) {
3232
}
3333
};
3434

35-
JSONPath.prototype.vm = {
35+
/**
36+
* In-browser replacement for NodeJS' VM.Script.
37+
*/
38+
class Script {
3639
/**
3740
* @param {string} expr Expression to evaluate
41+
*/
42+
constructor (expr) {
43+
this.code = expr;
44+
}
45+
46+
/**
3847
* @param {PlainObject} context Object whose items will be added
3948
* to evaluation
4049
* @returns {EvaluatedResult} Result of evaluated code
4150
*/
42-
runInNewContext (expr, context) {
51+
runInNewContext (context) {
52+
let expr = this.code;
4353
const keys = Object.keys(context);
4454
const funcs = [];
4555
moveToAnotherArray(keys, funcs, (key) => {
@@ -81,6 +91,10 @@ JSONPath.prototype.vm = {
8191
// eslint-disable-next-line no-new-func
8292
return (new Function(...keys, code))(...values);
8393
}
94+
}
95+
96+
JSONPath.prototype.vm = {
97+
Script
8498
};
8599

86100
export {JSONPath};

src/jsonpath.js

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -588,32 +588,34 @@ JSONPath.prototype._slice = function (
588588
JSONPath.prototype._eval = function (
589589
code, _v, _vname, path, parent, parentPropName
590590
) {
591-
if (code.includes('@parentProperty')) {
592-
this.currSandbox._$_parentProperty = parentPropName;
593-
code = code.replace(/@parentProperty/gu, '_$_parentProperty');
594-
}
595-
if (code.includes('@parent')) {
596-
this.currSandbox._$_parent = parent;
597-
code = code.replace(/@parent/gu, '_$_parent');
598-
}
599-
if (code.includes('@property')) {
600-
this.currSandbox._$_property = _vname;
601-
code = code.replace(/@property/gu, '_$_property');
602-
}
603-
if (code.includes('@path')) {
591+
this.currSandbox._$_parentProperty = parentPropName;
592+
this.currSandbox._$_parent = parent;
593+
this.currSandbox._$_property = _vname;
594+
this.currSandbox._$_root = this.json;
595+
this.currSandbox._$_v = _v;
596+
597+
const containsPath = code.includes('@path');
598+
if (containsPath) {
604599
this.currSandbox._$_path = JSONPath.toPathString(path.concat([_vname]));
605-
code = code.replace(/@path/gu, '_$_path');
606-
}
607-
if (code.includes('@root')) {
608-
this.currSandbox._$_root = this.json;
609-
code = code.replace(/@root/gu, '_$_root');
610600
}
611-
if ((/@([.\s)[])/u).test(code)) {
612-
this.currSandbox._$_v = _v;
613-
code = code.replace(/@([.\s)[])/gu, '_$_v$1');
601+
602+
const scriptCacheKey = 'script:' + code;
603+
if (!JSONPath.cache[scriptCacheKey]) {
604+
let script = code
605+
.replace(/@parentProperty/gu, '_$_parentProperty')
606+
.replace(/@parent/gu, '_$_parent')
607+
.replace(/@property/gu, '_$_property')
608+
.replace(/@root/gu, '_$_root')
609+
.replace(/@([.\s)[])/gu, '_$_v$1');
610+
if (containsPath) {
611+
script = script.replace(/@path/gu, '_$_path');
612+
}
613+
614+
JSONPath.cache[scriptCacheKey] = new this.vm.Script(script);
614615
}
616+
615617
try {
616-
return this.vm.runInNewContext(code, this.currSandbox);
618+
return JSONPath.cache[scriptCacheKey].runInNewContext(this.currSandbox);
617619
} catch (e) {
618620
throw new Error('jsonPath: ' + e.message + ': ' + code);
619621
}

test/test.errors.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ checkBuiltInVMAndNodeVM(function (vmType, setBuiltInState) {
2020
it('should throw with a bad filter', () => {
2121
expect(() => {
2222
jsonpath({json: {book: []}, path: '$..[?(@.category === category)]'});
23-
}).to.throw(Error, 'jsonPath: category is not defined: _$_v.category === category');
23+
}).to.throw(Error, 'jsonPath: category is not defined: @.category === category');
2424
});
2525

2626
it('should throw with a bad result type', () => {

0 commit comments

Comments
 (0)