diff --git a/lib/index.js b/lib/index.js index 8f5a832..432e94e 100755 --- a/lib/index.js +++ b/lib/index.js @@ -23,8 +23,7 @@ JSONPath.prototype.parent = function(obj, string) { assert.ok(string, "we need a path"); var node = this.nodes(obj, string)[0]; - var key = node.path.pop(); /* jshint unused:false */ - return this.value(obj, node.path); + return _get_value_by_path(obj, node.path, 1); } JSONPath.prototype.apply = function(obj, string, fn) { @@ -33,19 +32,37 @@ JSONPath.prototype.apply = function(obj, string, fn) { assert.ok(string, "we need a path"); assert.equal(typeof fn, "function", "fn needs to be function") - var nodes = this.nodes(obj, string).sort(function(a, b) { + return this._iterateNodes(obj, string, function(node) { + var key = node.path[node.path.length - 1]; + var parent = _get_value_by_path(obj, node.path, 1); + var val = node.value = fn.call(obj, parent[key]); + parent[key] = val; + }); +} + +JSONPath.prototype.forEachNode = function(obj, path, fn) { + + assert.ok(obj instanceof Object, "obj needs to be an object"); + assert.ok(path, "we need a path"); + assert.equal(typeof fn, "function", "fn needs to be function") + + return this._iterateNodes(obj, path, function(node) { + fn(new _NodeWrapper(obj, node.path, node.value)); + }); +} + +JSONPath.prototype._iterateNodes = function(obj, path, fn) { + + assert.ok(obj instanceof Object, "obj needs to be an object"); + assert.ok(path, "we need a path"); + assert.equal(typeof fn, "function", "fn needs to be function") + + var nodes = this.nodes(obj, path).sort(function(a, b) { // sort nodes so we apply from the bottom up return b.path.length - a.path.length; }); - nodes.forEach(function(node) { - var key = node.path.pop(); - var parent = this.value(obj, this.stringify(node.path)); - var val = node.value = fn.call(obj, parent[key]); - parent[key] = val; - }, this); - - return nodes; + return nodes.forEach(fn.bind(this)); } JSONPath.prototype.value = function(obj, path, value) { @@ -57,7 +74,7 @@ JSONPath.prototype.value = function(obj, path, value) { var node = this.nodes(obj, path).shift(); if (!node) return this._vivify(obj, path, value); var key = node.path.slice(-1).shift(); - var parent = this.parent(obj, this.stringify(node.path)); + var parent = _get_value_by_path(obj, node.path, 1); parent[key] = value; } return this.query(obj, this.stringify(path), 1).shift(); @@ -240,6 +257,49 @@ function _is_string(obj) { return Object.prototype.toString.call(obj) == '[object String]'; } +function _get_value_by_path(obj, path, skip) { + skip = skip || 0; + for (var i = 1; i < path.length - skip; ++i) + obj = obj[path[i]]; + return obj; +} + +var _NodeWrapper = function(root, path, value) { + this.root = root; + this.path = path; + this.value = value; +}; + +_NodeWrapper.prototype.parent = function(skip) { + skip = skip + 1 || 1; + if (skip > this.path.length) + throw Error("couldn't skip passed root object"); + var parentPath = this.path.slice(0, -skip); + var parentValue = _get_value_by_path(this.root, parentPath); + return new _NodeWrapper(this.root, parentPath, parentValue); +}; + +_NodeWrapper.prototype.key = function() { + return this.path[this.path.length - 1]; +}; + +_NodeWrapper.prototype.update = function(value) { + this.parent()[this.key()] = value; +}; + +_NodeWrapper.prototype.remove = function() { + var parent = this.parent(); + var key = this.key(); + var value = parent[key]; + + if (Array.isArray(parent)) + Array.prototype.splice.call(parent, key, 1); + else + delete parent[key]; + + return value; +}; + JSONPath.Handlers = Handlers; JSONPath.Parser = Parser;