Skip to content

Commit 79ce2e1

Browse files
committed
Support basic query operations without eval
1 parent 0c47cb2 commit 79ce2e1

File tree

2 files changed

+112
-8
lines changed

2 files changed

+112
-8
lines changed

lib/environment.js

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,74 @@ Environment.prototype.slice = function(segment, expression, object, path) {
111111
}
112112
};
113113

114-
Environment.prototype.match = function(segment, _v, _vname) {
115-
try {
116-
var $ = this.jsonObject;
117-
return _v && eval(segment.replace(/@/g, "_v"));
118-
}
119-
catch(e) {
120-
throw new SyntaxError("jsonPath: " + e.message + ": " + segment.replace(/@/g, "_v").replace(/\^/g, "_a"));
121-
}
114+
Environment.prototype.match = function(expression, object, _vname) {
115+
if(object === null) return object;
116+
117+
if(/[>=<]/.test(expression))
118+
return this.compareValue(expression, object);
119+
else if(/[-+*\/]/.test(expression))
120+
return this.computeValue(expression, object);
121+
else if(/\./.test(expression))
122+
return this.getProperty(expression, object);
123+
else
124+
throw new SyntaxError("jsonPath: invalid query syntax: " + expression);
125+
};
126+
127+
Environment.prototype.compareValue = function(expression, object) {
128+
return this.operate(object, expression, '>=<');
129+
};
130+
131+
Environment.prototype.getProperty = function(expression, object) {
132+
var parts = expression.match(/\.([^\s]+)/);
133+
var rhs;
134+
try {
135+
rhs = JSON.parse('"' + parts[1] + '"');
136+
}
137+
catch(e) {
138+
throw new SyntaxError("jsonPath: invalid property name: " + parts[1]);
139+
}
140+
return object[rhs];
141+
};
142+
143+
Environment.prototype.getValue = function(expression) {
144+
try {
145+
return JSON.parse(expression);
146+
}
147+
catch(e) {
148+
throw new SyntaxError("jsonPath: invalid right-hand-side value: " + expression);
149+
}
150+
};
151+
152+
Environment.prototype.computeValue = function(expression, object) {
153+
return this.operate(object, expression, '-+*/');
154+
};
155+
156+
Environment.prototype.operate = function(object, expression, operators) {
157+
var regex = new RegExp("([^\(\)" + operators + "]+)([" + operators + "]+)([^\(\)]+)");
158+
var parts = expression.match(regex);
159+
var lhs = this.getProperty(parts[1], object);
160+
var rhs = this.getValue(parts[3]);
161+
162+
switch(parts[2]) {
163+
case '==':
164+
return lhs == rhs;
165+
case '>=':
166+
return lhs >= rhs;
167+
case '<=':
168+
return lhs <= rhs;
169+
case '>':
170+
return lhs > rhs;
171+
case '<':
172+
return lhs < rhs;
173+
case '-':
174+
return lhs - rhs;
175+
case '+':
176+
return lhs + rhs;
177+
case '*':
178+
return lhs * rhs;
179+
case '/':
180+
return lhs / rhs;
181+
default:
182+
throw new SyntaxError("jsonPath: invalid query syntax: " + expression);
183+
}
122184
};

test/environment-test.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
var assert = require('assert'),
2+
vows = require('vows'),
3+
Environment = require("../lib/environment");
4+
5+
vows.describe('JSONPath').addBatch({
6+
'#match': {
7+
topic: new Environment({}, '', {}),
8+
9+
'matches (@.length-1)': function(env) {
10+
var obj = [1,2,3];
11+
assert.equal(env.match('(@.length-1)', obj), 2);
12+
},
13+
'matches @.isbn': function(env) {
14+
var obj = { isbn: 'abc123' };
15+
assert.equal(env.match('@.isbn', obj), 'abc123');
16+
},
17+
'matches @.price<10 true': function(env) {
18+
var obj = { price: 5 };
19+
assert.isTrue(env.match('@.price<10', obj));
20+
},
21+
'matches @.price<10 false': function(env) {
22+
var obj = { price: 10 };
23+
assert.isFalse(env.match('@.price<10', obj));
24+
},
25+
'matches @.price==10 true': function(env) {
26+
var obj = { price: 10 };
27+
assert.isTrue(env.match('@.price==10', obj));
28+
},
29+
'matches @.price==10 false': function(env) {
30+
var obj = { price: 11 };
31+
assert.isFalse(env.match('@.price==10', obj));
32+
},
33+
'returns null if there is nothing to match against': function(env) {
34+
assert.isNull(env.match('something', null));
35+
},
36+
'throws a syntax error for invalid statements': function(env) {
37+
assert.throws(function() {
38+
env.match('oogabooga', {});
39+
});
40+
}
41+
}
42+
}).export(module);

0 commit comments

Comments
 (0)