From 0611896d0a5939df1612141394017cea3b14fc13 Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Mon, 28 Nov 2016 11:05:48 +0200 Subject: [PATCH 1/3] Speedup internal operations with paths --- lib/index.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/index.js b/lib/index.js index 8f5a832..3a56611 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) { @@ -39,8 +38,8 @@ JSONPath.prototype.apply = function(obj, string, fn) { }); nodes.forEach(function(node) { - var key = node.path.pop(); - var parent = this.value(obj, this.stringify(node.path)); + 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; }, this); @@ -57,7 +56,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 +239,13 @@ 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; +} + JSONPath.Handlers = Handlers; JSONPath.Parser = Parser; From 0a9298b9da87ca455aac89c07bb566991422952d Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Mon, 28 Nov 2016 13:57:14 +0200 Subject: [PATCH 2/3] Add 'forEachNode' function As discussed in #41 --- lib/index.js | 81 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 8 deletions(-) diff --git a/lib/index.js b/lib/index.js index 3a56611..45938fe 100755 --- a/lib/index.js +++ b/lib/index.js @@ -32,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) { - // sort nodes so we apply from the bottom up - return b.path.length - a.path.length; - }); - - nodes.forEach(function(node) { + 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; - }, this); + }); +} + +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 nodes; + return this._iterateNodes(obj, path, function(node) { + fn(new _NodeWrapper(obj, node)); + }); +} + +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; + }); + + return nodes.forEach(fn.bind(this)); } JSONPath.prototype.value = function(obj, path, value) { @@ -246,6 +264,53 @@ function _get_value_by_path(obj, path, skip) { return obj; } +var _NodeWrapper = function(root, node) { + this._root = root; + this._node = node; +}; + +_NodeWrapper.prototype.value = function(skip) { + skip = skip || 0; + if (skip === 0) + return this._node.value; + return _get_value_by_path(this._root, this._node.path, skip); +}; + +_NodeWrapper.prototype.parent = function(skip) { + skip = (skip + 1) || 1; + return this.value(skip); +}; + +_NodeWrapper.prototype.path = function() { + return this._node.path; +}; + +_NodeWrapper.prototype.key = function(skip) { + skip = skip || 0; + var index = this._node.path.length - 1 - skip; + if (index === 0) return undefined; + return this._node.path[index]; +}; + +_NodeWrapper.prototype.update = function(value, skip) { + skip = skip || 0; + this.parent(skip)[this.key(skip)] = value; +}; + +_NodeWrapper.prototype.remove = function(skip) { + skip = skip || 0; + var parent = this.parent(skip); + var key = this.key(skip); + 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; From b044def7067cb40576513d207b2c8ad50bbd225d Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Fri, 2 Dec 2016 21:10:46 +0200 Subject: [PATCH 3/3] Remove 'skip' parameter. Make _NodeWrapper compatible with old 'node' object --- lib/index.js | 47 ++++++++++++++++++----------------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/lib/index.js b/lib/index.js index 45938fe..432e94e 100755 --- a/lib/index.js +++ b/lib/index.js @@ -47,7 +47,7 @@ JSONPath.prototype.forEachNode = function(obj, path, fn) { assert.equal(typeof fn, "function", "fn needs to be function") return this._iterateNodes(obj, path, function(node) { - fn(new _NodeWrapper(obj, node)); + fn(new _NodeWrapper(obj, node.path, node.value)); }); } @@ -264,43 +264,32 @@ function _get_value_by_path(obj, path, skip) { return obj; } -var _NodeWrapper = function(root, node) { - this._root = root; - this._node = node; -}; - -_NodeWrapper.prototype.value = function(skip) { - skip = skip || 0; - if (skip === 0) - return this._node.value; - return _get_value_by_path(this._root, this._node.path, skip); +var _NodeWrapper = function(root, path, value) { + this.root = root; + this.path = path; + this.value = value; }; _NodeWrapper.prototype.parent = function(skip) { - skip = (skip + 1) || 1; - return this.value(skip); -}; - -_NodeWrapper.prototype.path = function() { - return this._node.path; + 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(skip) { - skip = skip || 0; - var index = this._node.path.length - 1 - skip; - if (index === 0) return undefined; - return this._node.path[index]; +_NodeWrapper.prototype.key = function() { + return this.path[this.path.length - 1]; }; -_NodeWrapper.prototype.update = function(value, skip) { - skip = skip || 0; - this.parent(skip)[this.key(skip)] = value; +_NodeWrapper.prototype.update = function(value) { + this.parent()[this.key()] = value; }; -_NodeWrapper.prototype.remove = function(skip) { - skip = skip || 0; - var parent = this.parent(skip); - var key = this.key(skip); +_NodeWrapper.prototype.remove = function() { + var parent = this.parent(); + var key = this.key(); var value = parent[key]; if (Array.isArray(parent))