Skip to content

Commit 83f0f70

Browse files
committed
Refactored for better testability
1 parent 8a214f8 commit 83f0f70

File tree

5 files changed

+229
-227
lines changed

5 files changed

+229
-227
lines changed

lib/environment.js

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
var cache = {};
2+
3+
var Environment = module.exports = function(jsonObject, expression, options) {
4+
this.jsonObject = jsonObject;
5+
this.expression = this.normalize(expression);
6+
this.options = options;
7+
this.result = [];
8+
};
9+
10+
Environment.prototype.execute = function() {
11+
this.trace(this.expression, this.jsonObject, "$");
12+
return this.result.length ? this.result : false;
13+
};
14+
15+
Environment.prototype.normalize = function(expression) {
16+
if(cache[expression]) {
17+
return cache[expression];
18+
}
19+
20+
var subx = [];
21+
ret = expression.replace(/[\['](\??\(.*?\))[\]']/g, function($0,$1){return "[#"+(subx.push($1)-1)+"]";})
22+
.replace(/'?\.'?|\['?/g, ";")
23+
.replace(/;;;|;;/g, ";..;")
24+
.replace(/;$|'?\]|'$/g, "")
25+
.replace(/#([0-9]+)/g, function($0,$1){return subx[$1];});
26+
cache[expression] = ret;
27+
return ret.replace(/^\$;/,"");
28+
};
29+
30+
Environment.prototype.asPath = function(path) {
31+
var x = path.split(";"), p = "$";
32+
for (var i=1,n=x.length; i<n; i++)
33+
p += /^[0-9*]+$/.test(x[i]) ? ("["+x[i]+"]") : ("['"+x[i]+"']");
34+
return p;
35+
};
36+
37+
Environment.prototype.store = function(path, value) {
38+
if (path) {
39+
var storageValue = this.options.resultType == "PATH" ? this.asPath(path) : value;
40+
this.result.push(storageValue);
41+
}
42+
};
43+
44+
Environment.prototype.trace = function(expression, object, path) {
45+
var self = this;
46+
if (expression) {
47+
var segments = expression.split(";")
48+
var segment = segments.shift();
49+
var newExpression = segments.join(";");
50+
if (object && object.hasOwnProperty(segment))
51+
this.trace(newExpression, object[segment], path + ";" + segment);
52+
else if (segment === "*") {
53+
this.walk(object, function(property) {
54+
self.trace(property + ";" + newExpression, object, path);
55+
});
56+
}
57+
else if (segment === "..") {
58+
this.trace(newExpression, object, path);
59+
this.walk(object, function(property) {
60+
typeof object[property] === "object" &&
61+
self.trace("..;" + newExpression, object[property], path + ";" + property);
62+
});
63+
}
64+
else if (/,/.test(segment)) { // [name1,name2,...]
65+
var s = segment.split(/'?,'?/);
66+
for (var i=0,n=s.length; i<n; i++)
67+
this.trace(s[i]+";"+newExpression, object, path);
68+
}
69+
else if (/^\(.*?\)$/.test(segment)) { // [(newExpression)]
70+
var name = path.substr(path.lastIndexOf(";")+1);
71+
var extendedExpression = this.eval(segment, object, name) + ";" + newExpression;
72+
this.trace(extendedExpression, object, path);
73+
}
74+
else if (/^\?\(.*?\)$/.test(segment)) { // [?(newExpression)]
75+
this.walk(object, function(property) {
76+
if (self.eval(segment.replace(/^\?\((.*?)\)$/,"$1"), object[property], property))
77+
self.trace(property + ";" + newExpression, object, path);
78+
});
79+
}
80+
else if (/^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/.test(segment)) // [start:end:step] phyton slice syntax
81+
this.slice(segment, newExpression, object, path);
82+
} else {
83+
this.store(path, object);
84+
}
85+
};
86+
87+
Environment.prototype.walk = function(object, callback) {
88+
if (object instanceof Array) {
89+
for (var index = 0, n = object.length; index < n; index++)
90+
callback(index);
91+
}
92+
else if (typeof object === "object") {
93+
for (var property in object)
94+
if (object.hasOwnProperty(property))
95+
callback(property);
96+
}
97+
};
98+
99+
Environment.prototype.slice = function(segment, expression, object, path) {
100+
if (object instanceof Array) {
101+
var len=object.length, start=0, end=len, step=1;
102+
segment.replace(/^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/g, function($0,$1,$2,$3) {
103+
start=parseInt($1||start);
104+
end=parseInt($2||end);
105+
step=parseInt($3||step);
106+
});
107+
start = (start < 0) ? Math.max(0,start+len) : Math.min(len,start);
108+
end = (end < 0) ? Math.max(0,end+len) : Math.min(len,end);
109+
for (var i=start; i<end; i+=step)
110+
this.trace(i+";"+expression, object, path);
111+
}
112+
};
113+
114+
Environment.prototype.eval = 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+
}
122+
};

lib/jsonpath.js

Lines changed: 25 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,25 @@
1-
/* JSONPath 0.8.0 - XPath for JSON
2-
*
3-
* Copyright (c) 2007 Stefan Goessner (goessner.net)
4-
* Licensed under the MIT (MIT-LICENSE.txt) licence.
5-
*/
6-
7-
exports.eval = jsonPath;
8-
var cache = {};
9-
function jsonPath(obj, expr, arg) {
10-
var P = {
11-
resultType: arg && arg.resultType || "VALUE",
12-
result: [],
13-
normalize: function(expr) {
14-
if(cache[expr]) {
15-
return cache[expr];
16-
}
17-
18-
var subx = [];
19-
ret = expr.replace(/[\['](\??\(.*?\))[\]']/g, function($0,$1){return "[#"+(subx.push($1)-1)+"]";})
20-
.replace(/'?\.'?|\['?/g, ";")
21-
.replace(/;;;|;;/g, ";..;")
22-
.replace(/;$|'?\]|'$/g, "")
23-
.replace(/#([0-9]+)/g, function($0,$1){return subx[$1];});
24-
cache[expr] = ret;
25-
return ret;
26-
},
27-
asPath: function(path) {
28-
var x = path.split(";"), p = "$";
29-
for (var i=1,n=x.length; i<n; i++)
30-
p += /^[0-9*]+$/.test(x[i]) ? ("["+x[i]+"]") : ("['"+x[i]+"']");
31-
return p;
32-
},
33-
store: function(p, v) {
34-
if (p) P.result[P.result.length] = P.resultType == "PATH" ? P.asPath(p) : v;
35-
return !!p;
36-
},
37-
trace: function(expr, val, path) {
38-
if (expr) {
39-
var x = expr.split(";"), loc = x.shift();
40-
x = x.join(";");
41-
if (val && val.hasOwnProperty(loc))
42-
P.trace(x, val[loc], path + ";" + loc);
43-
else if (loc === "*")
44-
P.walk(loc, x, val, path, function(m,l,x,v,p) { P.trace(m+";"+x,v,p); });
45-
else if (loc === "..") {
46-
P.trace(x, val, path);
47-
P.walk(loc, x, val, path, function(m,l,x,v,p) { typeof v[m] === "object" && P.trace("..;"+x,v[m],p+";"+m); });
48-
}
49-
else if (/,/.test(loc)) { // [name1,name2,...]
50-
for (var s=loc.split(/'?,'?/),i=0,n=s.length; i<n; i++)
51-
P.trace(s[i]+";"+x, val, path);
52-
}
53-
else if (/^\(.*?\)$/.test(loc)) // [(expr)]
54-
P.trace(P.eval(loc, val, path.substr(path.lastIndexOf(";")+1))+";"+x, val, path);
55-
else if (/^\?\(.*?\)$/.test(loc)) // [?(expr)]
56-
P.walk(loc, x, val, path, function(m,l,x,v,p) { if (P.eval(l.replace(/^\?\((.*?)\)$/,"$1"),v[m],m)) P.trace(m+";"+x,v,p); });
57-
else if (/^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/.test(loc)) // [start:end:step] phyton slice syntax
58-
P.slice(loc, x, val, path);
59-
}
60-
else
61-
P.store(path, val);
62-
},
63-
walk: function(loc, expr, val, path, f) {
64-
if (val instanceof Array) {
65-
for (var i=0,n=val.length; i<n; i++)
66-
if (i in val)
67-
f(i,loc,expr,val,path);
68-
}
69-
else if (typeof val === "object") {
70-
for (var m in val)
71-
if (val.hasOwnProperty(m))
72-
f(m,loc,expr,val,path);
73-
}
74-
},
75-
slice: function(loc, expr, val, path) {
76-
if (val instanceof Array) {
77-
var len=val.length, start=0, end=len, step=1;
78-
loc.replace(/^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/g, function($0,$1,$2,$3){start=parseInt($1||start);end=parseInt($2||end);step=parseInt($3||step);});
79-
start = (start < 0) ? Math.max(0,start+len) : Math.min(len,start);
80-
end = (end < 0) ? Math.max(0,end+len) : Math.min(len,end);
81-
for (var i=start; i<end; i+=step)
82-
P.trace(i+";"+expr, val, path);
83-
}
84-
},
85-
eval: function(x, _v, _vname) {
86-
try { return $ && _v && eval(x.replace(/@/g, "_v")); }
87-
catch(e) { throw new SyntaxError("jsonPath: " + e.message + ": " + x.replace(/@/g, "_v").replace(/\^/g, "_a")); }
88-
}
89-
};
90-
91-
var $ = obj;
92-
if (expr && obj && (P.resultType == "VALUE" || P.resultType == "PATH")) {
93-
P.trace(P.normalize(expr).replace(/^\$;/,""), obj, "$");
94-
return P.result.length ? P.result : false;
95-
}
96-
}
1+
/* JSONPath 0.8.0 - XPath for JSON
2+
*
3+
* Copyright (c) 2007 Stefan Goessner (goessner.net)
4+
* Licensed under the MIT (MIT-LICENSE.txt) licence.
5+
*/
6+
7+
var Environment = require('./environment');
8+
9+
var JSONPath = module.exports = {
10+
eval: function jsonPath(jsonObject, expression, options) {
11+
if(!jsonObject)
12+
throw 'You must provide a JSON object';
13+
if(!expression)
14+
throw 'An expression is required';
15+
if(!options)
16+
options = {};
17+
if(!options.resultType)
18+
options.resultType = 'VALUE';
19+
if(options.resultType != "VALUE" && options.resultType != "PATH")
20+
throw 'Invalid options, resultType must be "VALUE" or "PATH"';
21+
22+
var env = new Environment(jsonObject, expression, options);
23+
return env.execute();
24+
}
25+
};

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@
44
"description": "A JS implementation of JSONPath",
55
"contributors": [
66
{ "name": "Stefan Goessner", "email": "subbu@subbu.org" },
7-
{ "name": "Mike Brevoort", "email": "mike@brevoort.com" }
7+
{ "name": "Mike Brevoort", "email": "mike@brevoort.com" },
8+
{ "name": "Derek Kastner", "email": "dkastner@gmail.com" }
89
],
9-
"version": "0.8.2",
10+
"version": "0.9.0",
1011
"repository": {
1112
"type": "git",
12-
"url": "git://github.com/s3u/JSONPath.git"
13+
"url": "git://github.com/dkastner/JSONPath.git"
1314
},
1415
"main" : "./lib/jsonpath",
1516
"dependencies": {},
1617
"devDependencies": {
17-
"nodeunit": "latest"
18+
"vows": "latest"
1819
}
1920
}

0 commit comments

Comments
 (0)