Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
olalonde committed Oct 26, 2012
0 parents commit 4602a64
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .gitignore
@@ -0,0 +1,15 @@
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz

pids
logs
results

node_modules
npm-debug.log
39 changes: 39 additions & 0 deletions README.md
@@ -0,0 +1,39 @@
# Install

npm install jsonselect;

# Usage

## select(obj, options)

Options:

- `only`: string|array - List of whitespace delimited paths (returned object will only contain those paths)
- `except`: string|array - List of whitespace delimited paths (returned object will not contain those paths)

Options can also be a string. See examples.

Paths can be (almost) any [JSONPath](http://goessner.net/articles/JsonPath/).

# Examples

```javascript
var select = require('jsonselect');

var a = select(obj, 'username email name.first friends -friends.password');
// equivalent to
var a2 = select(obj, { only: 'username email name.first friends', except: 'friends.password'} );

var b = select(obj, '-password'); // everything except password
// equivalent to
var b2 = select(obj, { except: '-password' });
```

# Using with Mongoose

Coming soon!

# TODO

- better parser for normalized paths
- properly delete array element using splice/concat
145 changes: 145 additions & 0 deletions index.js
@@ -0,0 +1,145 @@
/*
* @todo
* - memoize string parsing
*/
var jsonpath = require('JSONPath')
_ = require('underscore');


// auto prepend annoying $. to first argument
evalpath = function(obj, path, getpath) {
if (getpath) {
var paths = jsonpath.eval(obj, '$.' + path, { resultType: 'PATH' });
paths.forEach(function (path, index) {
paths[index] = path.substr(1); // strip leading $
});
return paths;
}

return jsonpath.eval(obj, '$.' + path);
};

/**
* A path is a [JSONPath](http://goessner.net/articles/JsonPath/)
*
* Options:
* - `only`: string|array - List of whitespace delimited paths (returned object will only contain those paths)
* - `except`: string|array - List of whitespace delimited paths (returned object will not contain those paths)
*
* @param {Object} object to filter
* @param {Object|String} options Options hash or string of paths prefixed by + or -.
*/
module.exports = function(obj, options) {
if (typeof options === 'string') {
options = parseOptions(options);
}

if (!(typeof options === 'object'))
throw new Error('Second argument must be an object or a string.');

var only = options.only || [];
var except = options.except || [];

if (typeof only === 'string') only = only.split(/ +/g);
if (typeof except === 'string') except = except.split(/ +/g);

// if only is not set, start with a copy of the original object
var ret = (only.length > 0) ? {} : _.extend({}, obj);

only.forEach(function(path) {
var normalizedPaths = evalpath(obj, path, true);
var values = evalpath(obj, path);
normalizedPaths.forEach(function (normalizedPath, index) {
setPath(ret, normalizedPath, values[index]);
});
});
except.forEach(function(path) {
var normalizedPaths = evalpath(obj, path, true);
//console.log(normalizedPaths);
normalizedPaths.forEach(function (normalizedPath, index) {
unsetPath(ret, normalizedPath);
});
});
return ret;
}

function setPath (obj, path, value) {
return modifyPath('set', obj, path, value);
}
function unsetPath (obj, path) {
return modifyPath('unset', obj, path);
}
function modifyPath(type, obj, path, value) {
//remove leading [ and trailing ]
path = path.substr(1, path.length - 2);
// @todo: tokenize better than this: might bug if we have ['a'][0]['b][b']['et\'c.']
path = path.split('][');

var chain = [];
path.forEach(function(property) {
// if string, remove trailing/leading '
if (property.indexOf('\'') !== -1) {
property = property.substr(1, property.length-2);
}
else {
property = parseInt(property);
}
chain.push(property);
});

var root = obj;
while (chain.length > 0) {
property = chain.shift();
if (chain.length === 0) {
if (type === 'unset') {
delete root[property];
}
else
root[property] = value;
break;
}
if (type === 'set' && root[property] === undefined) {
root[property] = (typeof property === 'number') ? [] : {};
}
root = root[property];
}
return obj;
console.log('done');
//eval('property = oldObj' + path);


}

// Recursive
function traverse (obj, cb, parent, key) {
if (obj instanceof Array) {
obj.forEach(function(child, index) {
traverse(child, cb, obj, index);
});
}
else if (typeof obj == 'object') {
for(var key in obj) {
traverse(obj[key], cb, obj, key);
}
}
cb(obj, parent, key);
}


function parseOptions (str) {
var only = [], except = [];

var regex = {
only: /\+[^ ]+/g
, except: /\-[^ ]+/g
}

str.replace(regex.only, function(path) {
only.push(path.substr(1)); // strip + or -
});
str.replace(regex.except, function(path) {
except.push(path.substr(1)); // strip + or -
});

return {only: only, except: except};
}
22 changes: 22 additions & 0 deletions package.json
@@ -0,0 +1,22 @@
{
"name": "jsonselect",
"version": "0.0.1",
"description": "Filter out JSON field using Mongoose's select syntax.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": "",
"keywords": [
"mongoose",
"json",
"filter",
"utility"
],
"author": "Olivier Lalonde <olalonde@gmail.com>",
"license": "BSD",
"dependencies": {
"JSONPath": "",
"underscore": ""
}
}
19 changes: 19 additions & 0 deletions test/index.js
@@ -0,0 +1,19 @@
var util = require('util');
var filter = require('../');

var user = {
username: 'Oli'
, password: 'secret password'
, friends: [
{ username: 'bob', password: 'bob pass' }
, { username: 'joe', password: 'joe pass' }
, { username: 'jeff', password: 'jeff pass' }
]
, name: {
first: 'Olivier'
, last: 'Lalonde'
}
}

console.dir(filter(user, { only: 'friends username name.first', except: '.password' }));
console.log(filter(user, 'username friends -friends[:2]'));

0 comments on commit 4602a64

Please sign in to comment.