npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

json-path-processor

v0.2.5

Published

A common data processor target to process data without running try catch hell, and get/set properties of nested objects.

Downloads

530

Readme

json-path-processor

JsonPathProcessor (JPP) - A common data processor target to process data without running try catch hell, and get/set properties of nested objects.

npm version npm download Dependency Status Build Status Test Coverage Code Climate License

Sauce Test Status

Installation

npm install json-path-processor

In browser:

<script src="dist/jpp.js"></script>

In an AMD loader:

require('jpp', function (jpp) {/*....*/});

In nodejs:

var jpp =  require('json-path-processor');

Features

  • Chainning
  • Iterate objects by simplified JSONPath
    • similar packages: dotty , dot-access , deep-get-set , traverse, lodash-deep
  • Catch all throwed error automatically

Upgrade Notice

  • Now JPP stop using lodash and move to ES5. For none ES5 browsers/enviromnent, you should use polyfills to ensure JPP works well. You can try polyfill.io or polyfills.io.

  • Some lodash like method behavior changed, please refer to document.

Usage

var jpp = require('json-path-processor');

// I wanna update all product title
data = jpp(data).each('product.title', function (V) {
    return something(V);
}).value();

// Ya, handle all product title and promotion description
// almost all jpp methods are chainable
data = jpp(data).each('extra.promotion', function (O) {
    O.description = someValue;
    return O;
}).each('product.title', someUtilFunc).value();

// jpp play on the object reference so you even do not need to assign back!
jpp(data).each('product', assignProductDefault);
jpp(data).each('product', changeProductDetail);
output(data);

// chaining is cool, right?
jpp(data)
.each('product', playTheProduct)
.each('product.title', fixProductTitle);
output(data);

API document and example

  • jpp(data) : create the JPP chainning object by data.
var J = jpp(['any', 'data', {or: {recursive: {'object'}}}]);
  • jpp(data, path) : a shortcut of jpp(data).value(path)
console.log(jpp({a: {b: 'OK'}}, 'a.b')); // will get 'OK'
console.log(jpp({a: {b: 'OK'}}, 'a.c.d')); // will get undefined
  • .value(path) : get value by JSON path. This method can not be chainned. When path is undefined or '' or '$', get whole data.
console.log(jpp([1, 3, 5]).value()); // will get [1, 3, 5]
console.log(jpp({a: {b: 'OK'}}).value('a.b')); // will get 'OK'
console.log(jpp({a: {b: 'OK'}}).value('a.c.d')); // will get undefined
  • .get(path) : get new JPP object by JSON path. All chainned methods on this is different from root object.
console.log(jpp({a: {b: 'OK'}}).get('a').get('b').value()); // will get 'OK'
  • .set(path, value, create) : set new value by JSON path. When value is a function, execute the function with first argument as old value. the return value of the callback function will be assigned. When create exists, create new object by the JSON path, and create will be used as default value to be assigned when the callback function throws exception.
// will get {a: {b: 'OK', c:[1, 3]}}
console.log(jpp({a: {b: 'OK', c: [1, 4]}}).set('a.c.1', 3).value());

// will get {a: {b: 'BAD', c:[1, 4]}}
console.log(jpp({a: {b: 'OK', c: [1, 4]}}).set('a.b', 'BAD').value());

// will get {a: {b: 'OK', c:[1, 4]}}
console.log(jpp({a: {b: 'OK', c: [1, 4]}}).set('a.b.c.d', 'OK?').value());

// set failed ... WE CAN NOT CONVERT ARRAY TO OBJECT
console.log(jpp({a: {b: 'OK', c: [1, 4]}}).set('a.b.c.d', 'OK?', true).value());

// a.b.c[2 ~ 9] will become undefined ... ARRAY SIZE AUTO EXPEND IN JAVASCRIPT
console.log(jpp({a: {b: 'OK', c: [1, 4]}}).set('a.c.10', 'OK?', true).value());
  • .copy(from, to, skip) : copy value from one JSON path to another. When the JSON path not found, new object will be created. To prevent new object creation, pass skip as true as 3rd param.
// will get {a: {b: 'OK', c:[1, 4], d: 4}}
console.log(jpp({a: {b: 'OK', c: [1, 4]}}).copy('a.c.1', 'a.d').value());
  • .del(path) : delete a key by JSON path. When the path exists, last key will be deleted; when it do not exist, do nothing.
// will get {a: {b: 'OK', c: [1, 4]}}
console.log(jpp({a: {b: 'OK', c: [1, 4]}}).del('a.b.c').value());

// will get {a: {b: {}}
console.log(jpp({a: {b: {c: {d: 2, q: 1}}}}).del('a.b.c').value());
  • .move(from, to) : move values from a path to another. when the origin path not found, do nothing.
// will get {a: {b: 'OK', d: [1, 4]}}
console.log(jpp({a: {b: 'OK', c: [1, 4]}}).move('a.c', 'a.d').value());
  • .range(path, args...) : Works like lodash.range() , create range and set the array into the path.
// will get {a: {b: 1, c: [0, 1, 2]}}
console.log(jpp({a: {b: 1}}}).range('a.c', 3).value());

// will get {a: {b: 1, c: [3, 4]}}
console.log(jpp({a: {b: 1}}}).range('a.c', 3, 5).value());

// will get {a: {b: 1, c: [2, 5, 8, 11]}}
console.log(jpp({a: {b: 1}}}).range('a.c', 2, 12, 3).value());
  • .find(path, args...) : Works like lodash.find() , return the value. This method can not be chainned.
// will get 3
console.log(jpp({a: {b: [0, 3, 4]}}}).find('a.b', function (O) {return O%2 > 0}));
  • .findLast(path, args...) : Works like lodash.findLast() , return the value. This method can not be chainned.
// will get 5
console.log(jpp({a: {b: [1, 3, 4, 5]}}}).find('a.b', function (O) {return O%2 > 0}));
  • .each(path, function (value, key) {...}) : Works like Array.map(), the callback arguments are: value, index. The return value of callback will be assigned back to JPP object. You can apply second callback function for fallback when the path is not found or not array. When your callback return undefined or throws, the item in array will not be changed.
console.log(jpp({a: {b: [1, 3, 5]}}).each('a.b', function (V) {
    return V * 2;
}).value());  // will get {a: {b: [2, 6, 10]}}

console.log(jpp({a: {b: [1, 3, 5]}}).each('a.b', function (V, I) { // I as index
    return V * I;
}).value());  // will get {a: {b: [0 , 3, 10]}}

// fallback when a.c is not array or not object or not found
console.log(jpp({a: {b: [1, 3, 5]}}).each('a.c', function (V) {
    return V * I;
}, function (O) {
    return 'ERROR'
}).value());  // will get {a: {b: [1 , 3, 5], c: 'ERROR'}}
  • .forIn(path, function (value, key) {...}) : Works like for (I in O), the callback arguments are: value, key. The return value of callback will be assigned back to JPP object. You can apply second callback function for fallback when the path is not found or not object. When your callback return undefined or throws, the item in object will not be changed.
// will get {a: 'OK!', b: 'BAD!', length: '9!'} 
// forIn() will not think object with length property as array.
console.log(jpp({a: 'OK', b: 'BAD', length: 9}).forIn('$', function (V, I) {
    return V + '!';
}).value()); 

// fallback when a.c is not array or not object or not found
console.log(jpp({a: {b: [1, 3, 5]}}).forIn('a.c', function (V) {
    return V * I;
}, function (O) {
    return 'ERROR'
}).value());  // will get {a: {b: [1, 3, 5], c: 'ERROR'}}
  • .filter(path, function (value, key) {...}) : Works like Array.filter() but also works well on object. the callback are: value, index|key. The filtered result will be assigned back to JPP object.
// will get {a: {b: {c: [3, 5], d:5}}}
console.log(jpp({a: {b: {c: [2, 3, 4, 5], d: 5}}}).filter('a.b.c', function (V) {
    return V % 2 > 0;
}).value()); 
  • .concat(path, path, path ...) : search for all values by proviced JSON path, then concat all arraies into one. None array value will be skipped. When array size >= 1, assign the concated array back to first path. Or, do nothing.
// will get {a: {b: {c: [1, 3], d:5}, d: [1, 3, 1,3]}}
console.log(jpp({a: {b: {c: [1, 3], d: 5}}}).concat('a.d', 'a.b.c', 'a.b.c').value());

Supported JSON Path

We only support absolute JSON Path and receive only one item.

  • $ : refer to self
  • $.foo.bar or .foo.bar or foo.bar or foo['bar'] or ['foo']['bar'] : refer to foo then bar
  • $.foo.3.bar or foo.3.bar or foo['3'].bar or foo['3']['bar'] : refer to foo then 4th item then bar

When you need to deal with . inside your property name, you can just use the array syntax:

// var result = data.sites['google.com'].pageRank.score;
// prevent exception by jpp way:
var result = jpp(data).value("sites['google.com'].pageRank.score");

TODO handle \ escape inside the array syntax

The long story

All our life is to handle data....with a loop. Let's start from a basic loop:

for (I in data) {
    data[I] = something(data[I]);
}

To make jslint happy or ensure the loop correct, we should add property check:

for (I in data) {
    if (data.hasOwnProperty(I)) {
        data[I] = something(data[I]);
    }
}

In real life, data is not always ready. We must handle none data case:

if (data && is_object(data)) {
    for (I in data) {
        if (data.hasOwnProperty(I)) {
            data[I] = something(data[I]);
        }
    }
}

Furthermore, please catch something() because they may throw some error.

if (data && is_object(data)) {
    for (I in data) {
        if (data.hasOwnProperty(I)) {
            try {
                data[I] = something(data[I]);
            } catch (E) {
                handle_error(E);
            }
        }
    }
}

The more assign in the loop, the more try/catch you need.

if (data && is_object(data)) {
    for (I in data) {
        if (data.hasOwnProperty(I)) {
            try {
                data[I].title = something(data[I].title);
            } catch (E) {
                handle_error(E);
            }
            try {
                data[I].desciption = something(data[I].description);
            } catch (E) {
                handle_error(E);
            }
            try {
                data[I].url = something(data[I].url);
            } catch (E) {
                handle_error(E);
            }
        }
    }
}

The loop becomes a nightmare now, right? Let's use lodash to reduce indents in the loop:

_(data).each(function(V) {
    try {
        V.title = something(V.title);
    } catch (E) {
        handle_error(E);
    }
    try {
        V.desciption = something(V.description);
    } catch (E) {
        handle_error(E);
    }
    try {
        V.url = something(V.url);
    } catch (E) {
        handle_error(E);
    }
});

But, lodash still can not reduce the try/catch hell for you. Now, JsonPathProcessor help on this!