Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ Returns the parent of the first matching element.

#### jp.apply(obj, pathExpression, fn)

Runs the supplied function `fn` on each matching element, and replaces each matching element with the return value from the function. The function accepts the value of the matching element as its only parameter. Returns matching nodes with their updated values.
Runs the supplied application function `fn` on each matching element, and replaces each matching element with the return value from the function. The function accepts the value of the matching element as its only parameter. Returns matching nodes with their updated values.


```javascript
Expand All @@ -166,6 +166,8 @@ var nodes = jp.apply(data, '$..author', function(value) { return value.toUpperCa
// ]
```

If the supplied application function returns the special value `:delete:` the node will be deleted and not updated as is otherwise the default case.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kristianmandrup Why do you need special :delete: value you can simply detect when callback returns undefined.

  • What if I want to replace the value with literal :delete:.
    IMHO, it's bad practice to create magic values.


#### jp.parse(pathExpression)

Parse the provided JSONPath expression into path components and their associated operations.
Expand Down
39 changes: 38 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var assert = require('assert');
var dict = require('./dict');
var Parser = require('./parser');
var Handlers = require('./handlers');
var remove = require('lodash.remove');

var JSONPath = function() {
this.initialize.apply(this, arguments);
Expand All @@ -27,6 +28,10 @@ JSONPath.prototype.parent = function(obj, string) {
return this.value(obj, node.path);
}

function isObject(obj) {
return obj === Object(obj);
}

JSONPath.prototype.apply = function(obj, string, fn) {

assert.ok(obj instanceof Object, "obj needs to be an object");
Expand All @@ -38,11 +43,43 @@ JSONPath.prototype.apply = function(obj, string, fn) {
return b.path.length - a.path.length;
});

function removeFromParent(parent, val) {
if (!isObject(val)) return;
if (!val.remove) return;

if (typeof val.remove !== 'function') {
if (!isObject(val.remove)) return;

// key: 'id'
// match: v.id

var key = val.remove.key;
var match = val.remove.match;

val.remove = function(obj) {
return obj[key] === match;
}
}

// use special remove function to remove this node from parent Array
remove(parent, val.remove)
}

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;

if (Array.isArray(parent)) {
removeFromParent(parent, val)
} else {
// parent must be an Object
if (val === ':delete:') {
delete parent[key];
} else {
parent[key] = val;
}
}
}, this);

return nodes;
Expand Down
14 changes: 8 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
{
"name": "jsonpath",
"description": "Query JavaScript objects with JSONPath expressions. Robust / safe JSONPath engine for Node.js.",
"version": "0.2.7",
"version": "0.2.8",
"author": "david@fmail.co.uk",
"scripts": {
"postinstall": "node lib/aesprim.js > generated/aesprim-browser.js",
"test": "mocha -u tdd test && jscs lib && jshint lib",
"generate": "node bin/generate_parser.js > generated/parser.js"
},
"dependencies": {
"esprima": "1.2.2",
"jison": "0.4.13",
"static-eval": "0.2.3",
"underscore": "1.7.0"
"esprima": "^2.0.0",
"jison": "^0.4.17",
"lodash.filter": "^4.6.0",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kristianmandrup I don't see where filter is used?

"lodash.remove": "^4.7.0",
"static-eval": "^1.1.1",
"underscore": "^1.8.3"
},
"browser": {
"./lib/aesprim.js": "./generated/aesprim-browser.js"
Expand All @@ -24,7 +26,7 @@
"grunt-contrib-uglify": "0.9.1",
"jscs": "1.10.0",
"jshint": "2.6.0",
"mocha": "2.1.0"
"mocha": "^3.1.0"
},
"repository": {
"type": "git",
Expand Down
59 changes: 59 additions & 0 deletions test/sugar.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,65 @@ suite('sugar', function() {
assert.equal(data.z.a, 101);
});

test('apply method deletes value', function() {
var data = { a: 1, b: 2, c: 3, z: { a: 100, b: 200 } };
jp.apply(data, '$..a', function(v) { return ':delete:' });
assert.ok(Object.keys(data).indexOf('a') < 0);
});

test('apply method deletes object from Array specified by remove function', function() {
var data = {
a: [
{
id: 'book',
price: 100
},
{
id: 'car',
price: 3456
}
],
b: 2
};

jp.apply(data, '$..a[0]', function(v) {
return {
remove: (obj) => {
return obj.id === v.id;
}
}
});

assert.equal(data.a[0].id, 'car');
});

test('apply method deletes object from Array specified by special remove object', function() {
var data = {
a: [
{
id: 'book',
price: 100
},
{
id: 'car',
price: 3456
}
],
b: 2
};

jp.apply(data, '$..a[0]', function(v) {
return {
remove: {
key: 'id',
match: v.id
}
}
});

assert.equal(data.a[0].id, 'car');
});

test('apply method applies survives structural changes', function() {
var data = {a: {b: [1, {c: [2,3]}]}};
jp.apply(data, '$..*[?(@.length > 1)]', function(array) {
Expand Down