UNPKG

6.32 kBJavaScriptView Raw
1var assert = require('assert');
2var dict = require('./dict');
3var Parser = require('./parser');
4var Handlers = require('./handlers');
5
6var JSONPath = function() {
7 this.initialize.apply(this, arguments);
8};
9
10JSONPath.prototype.initialize = function() {
11 this.parser = new Parser();
12 this.handlers = new Handlers();
13};
14
15JSONPath.prototype.parse = function(string) {
16 assert.ok(_is_string(string), "we need a path");
17 return this.parser.parse(string);
18};
19
20JSONPath.prototype.parent = function(obj, string) {
21
22 assert.ok(obj instanceof Object, "obj needs to be an object");
23 assert.ok(string, "we need a path");
24
25 var node = this.nodes(obj, string)[0];
26 var key = node.path.pop(); /* jshint unused:false */
27 return this.value(obj, node.path);
28}
29
30JSONPath.prototype.apply = function(obj, string, fn) {
31
32 assert.ok(obj instanceof Object, "obj needs to be an object");
33 assert.ok(string, "we need a path");
34 assert.equal(typeof fn, "function", "fn needs to be function")
35
36 var nodes = this.nodes(obj, string).sort(function(a, b) {
37 // sort nodes so we apply from the bottom up
38 return b.path.length - a.path.length;
39 });
40
41 nodes.forEach(function(node) {
42 var key = node.path.pop();
43 var parent = this.value(obj, this.stringify(node.path));
44 var val = node.value = fn.call(obj, parent[key]);
45 parent[key] = val;
46 }, this);
47
48 return nodes;
49}
50
51JSONPath.prototype.value = function(obj, path, value) {
52
53 assert.ok(obj instanceof Object, "obj needs to be an object");
54 assert.ok(path, "we need a path");
55
56 if (arguments.length >= 3) {
57 var node = this.nodes(obj, path).shift();
58 if (!node) return this._vivify(obj, path, value);
59 var key = node.path.slice(-1).shift();
60 var parent = this.parent(obj, this.stringify(node.path));
61 parent[key] = value;
62 }
63 return this.query(obj, this.stringify(path), 1).shift();
64}
65
66JSONPath.prototype._vivify = function(obj, string, value) {
67
68 var self = this;
69
70 assert.ok(obj instanceof Object, "obj needs to be an object");
71 assert.ok(string, "we need a path");
72
73 var path = this.parser.parse(string)
74 .map(function(component) { return component.expression.value });
75
76 var setValue = function(path, value) {
77 var key = path.pop();
78 var node = self.value(obj, path);
79 if (!node) {
80 setValue(path.concat(), typeof key === 'string' ? {} : []);
81 node = self.value(obj, path);
82 }
83 node[key] = value;
84 }
85 setValue(path, value);
86 return this.query(obj, string)[0];
87}
88
89JSONPath.prototype.query = function(obj, string, count) {
90
91 assert.ok(obj instanceof Object, "obj needs to be an object");
92 assert.ok(_is_string(string), "we need a path");
93
94 var results = this.nodes(obj, string, count)
95 .map(function(r) { return r.value });
96
97 return results;
98};
99
100JSONPath.prototype.paths = function(obj, string, count) {
101
102 assert.ok(obj instanceof Object, "obj needs to be an object");
103 assert.ok(string, "we need a path");
104
105 var results = this.nodes(obj, string, count)
106 .map(function(r) { return r.path });
107
108 return results;
109};
110
111JSONPath.prototype.nodes = function(obj, string, count) {
112
113 assert.ok(obj instanceof Object, "obj needs to be an object");
114 assert.ok(string, "we need a path");
115
116 if (count === 0) return [];
117
118 var path = this.parser.parse(string);
119 var handlers = this.handlers;
120
121 var partials = [ { path: ['$'], value: obj } ];
122 var matches = [];
123
124 if (path.length && path[0].expression.type == 'root') path.shift();
125
126 if (!path.length) return partials;
127
128 path.forEach(function(component, index) {
129
130 if (matches.length >= count) return;
131 var handler = handlers.resolve(component);
132 var _partials = [];
133
134 partials.forEach(function(p) {
135
136 if (matches.length >= count) return;
137 var results = handler(component, p, count);
138
139 if (index == path.length - 1) {
140 // if we're through the components we're done
141 matches = matches.concat(results || []);
142 } else {
143 // otherwise accumulate and carry on through
144 _partials = _partials.concat(results || []);
145 }
146 });
147
148 partials = _partials;
149
150 });
151
152 return count ? matches.slice(0, count) : matches;
153};
154
155JSONPath.prototype.stringify = function(path) {
156
157 assert.ok(path, "we need a path");
158
159 var string = '$';
160
161 var templates = {
162 'descendant-member': '..{{value}}',
163 'child-member': '.{{value}}',
164 'descendant-subscript': '..[{{value}}]',
165 'child-subscript': '[{{value}}]'
166 };
167
168 path = this._normalize(path);
169
170 path.forEach(function(component) {
171
172 if (component.expression.type == 'root') return;
173
174 var key = [component.scope, component.operation].join('-');
175 var template = templates[key];
176 var value;
177
178 if (component.expression.type == 'string_literal') {
179 value = JSON.stringify(component.expression.value)
180 } else {
181 value = component.expression.value;
182 }
183
184 if (!template) throw new Error("couldn't find template " + key);
185
186 string += template.replace(/{{value}}/, value);
187 });
188
189 return string;
190}
191
192JSONPath.prototype._normalize = function(path) {
193
194 assert.ok(path, "we need a path");
195
196 if (typeof path == "string") {
197
198 return this.parser.parse(path);
199
200 } else if (Array.isArray(path) && typeof path[0] == "string") {
201
202 var _path = [ { expression: { type: "root", value: "$" } } ];
203
204 path.forEach(function(component, index) {
205
206 if (component == '$' && index === 0) return;
207
208 if (typeof component == "string" && component.match("^" + dict.identifier + "$")) {
209
210 _path.push({
211 operation: 'member',
212 scope: 'child',
213 expression: { value: component, type: 'identifier' }
214 });
215
216 } else {
217
218 var type = typeof component == "number" ?
219 'numeric_literal' : 'string_literal';
220
221 _path.push({
222 operation: 'subscript',
223 scope: 'child',
224 expression: { value: component, type: type }
225 });
226 }
227 });
228
229 return _path;
230
231 } else if (Array.isArray(path) && typeof path[0] == "object") {
232
233 return path
234 }
235
236 throw new Error("couldn't understand path " + path);
237}
238
239function _is_string(obj) {
240 return Object.prototype.toString.call(obj) == '[object String]';
241}
242
243JSONPath.Handlers = Handlers;
244JSONPath.Parser = Parser;
245
246var instance = new JSONPath;
247instance.JSONPath = JSONPath;
248
249module.exports = instance;