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

@fsslc/json-operator

v1.3.3

Published

JSON operator for delete, set and merge using jsonpath

Downloads

3

Readme

JSON Operator

Perform efficient path based operations on JSON Objects (or most Javascript data object).

Uses jsonpath with new delete option for apply.

Install

npm i json-operator --save

API

Note: The arguments indent, path and opts are always optional.

Constructor

  • constructor(target, {path, jsonpath, mergeFun, createMerge})

Setters and getters

  • .path : default operator path
  • .target : target object
  • .jp : jsonpath engine
  • .createMerge : function(opts)

createMerge can be set to factory function which returns custom merge function used by merge if present. The opts will be the options passed to merge enriched with targetObj and mergeObj

Getters

  • .history : history list of path and operation made
  • .result : modified target object
  • .lastPath : last path mutation applied

Display

  • targetAsStr(indent = 2) : get prettified string of target obj
  • display(indent, logger) : display prettified string of target obj using logger (default console.log)

Read values

  • query(path) : get all match results in list
  • value(path) : get first match result
  • parent(path) : parent of first match

Path operation scope*

  • with(path) : set withPath used in new operations scope
  • withSame() : use lastPath as withPath in new operations scope
  • and() : alias to withSame
  • done() : exit current operation scope

Mutations

opts is an optional argument which may contain:

  • path : to override current scope path
  • condition : function to be called with node value and context (ie. parent and key) to decide if mutation/action should be performed.

This condition function has the signature:

function(value, {parent, key}) : boolean

Insert

  • concat(insertObjs, opts = {}) : concatenate object(s) at the end of parent Array
  • prepend(insertObj, opts = {}) : prepend object before matching node
  • append(insertObj, opts = {}) : append object after matching node
  • insertAt(insertObj, key) : insert object at key on target object

Delete

  • delete(opts = {}) : delete matches
  • splice(opts = {}) : splice on target object (start, deleteCount, insertObj options)

Set

  • overwrite(obj, opts = {}) : set match to new object

Merge

  • merge(obj, opts = {}) : merge matches with new object
  • deepMerge(obj, opts = {}) : deep merge matches with new object
  • reverseMerge(obj, opts = {}) : merge matches with new object

Delegation (general purpose)

  • apply(fn, path) : delegates to jsonpath.apply
  • filter(fn, path) : delegates to jsonpath.filter

Flatten/Unflatten

Use the flatten and unflatten methods. First you must set the flattener to use, such as flat or soon to be flat2 (WIP)

Normalize

Use the normalize method which uses normalizr

For flatten and normalize usage examples, see the Changelog.md or the documentation for each module (flat and normalizr).

All mutators can be chained beautifully :)

let finalResult = operator
  .delete({path: delPath.x})
  .merge(obj, {path: mergePaths.a, condition: mySpecialCondition})
  .withSame() // use latest path for new scope
    .overwrite({x: 2}) // overwrite using latest scope path
    .merge(partials[0], {path: mergePaths.b}) // merge using custom path
    .and() // use latest path for new scope
      .merge(objX) // reusing latest path
      .with(specialPath) // create scope with path
        .merge(partials[1]) // merge using scope path
        .with(sweetPath) // create inner scope path
          .merge(partials[2]) // merge using current scope path
        .done() // close current scope
      .done()
    .done()
  .done()
  .result;

You can add your custom functions directly to the operator object or the class JsonOperator.prototype.

Usage

Given the following book store:

{
  "store": {
    "book": [ 
      {
        "category": "reference",
        "author": "Nigel Rees",
        "title": "Sayings of the Century",
        "price": 8.95
      }, {
        "category": "fiction",
        "author": "Evelyn Waugh",
        "title": "Sword of Honour",
        "price": 12.99
      }, {
        "category": "fiction",
        "author": "Herman Melville",
        "title": "Moby Dick",
        "isbn": "0-553-21311-3",
        "price": 8.99
      }, {
         "category": "fiction",
        "author": "J. R. R. Tolkien",
        "title": "The Lord of the Rings",
        "isbn": "0-395-19395-8",
        "price": 22.99
      }
    ],
    "bicycle": {
      "color": "red",
      "price": 19.95
    }
  }
}

Note: Books are indexed from 0 as normal, so book [2] is the 3rd book in the list.

We perform the following operations to:

  • get the value of book [2]
  • get the value of book [3]
  • set the default opertor path to book [3]
  • overwrite book 3 with book [2]
  • merge {price:100} onto book [3]
  • reverse order merge {rating: 4} onto book [3]
  • delete book [3]
  • display full target object after operations

Full API example

const store = require('./store')
const operator = new JsonOperator(store)

let paths = {
  book2: '$..book[2]',
  book3: '$..book[3]'
}

// query for all matching path
let book2results = operator.query(paths.book2);
console.log('original book 2', book2results)

// get the first match for book 2
let book2val = operator.value(paths.book2);
console.log('original book 2 value', book2val)

// let book2par = operator.parent(paths.book2);
// console.log('original book 2 parent', book2par)

// get the first match for book 3 
let book3 = operator.value(paths.book3);
console.log('original book 3', book3)

// set default path to use for the following ops
operator.path = paths.book3;

// overwrite book3 with book2
operator.overwrite(book2val)
let book3Set = operator.value(); 

console.log('original book 3', book3)
console.log('book 3 set', book3Set)

// overwrite book2 with value found at book3
operator.merge({price: 100})
let book3Merged = operator.value()

console.log('original book 3', book3)
console.log('book 3 set', book3Set)
console.log('book 3 set', book3Merged)

// operator.merge({price: 100}, {reverse: true})
operator.deepMerge({rating: 4}, {reverse: true, path: mergePath})
operator.reverseMerge({rating: 4})
operator.reverseMerge({rating: 4}, {path})

operator.delete()
let book3deleted = operator.value()
console.log('book 3 deleted', book3deleted)
console.log('store after all operations', operator.targetAsStr())

Custom merge

You can define a custom merge function as follows:

// return merge function dependent on option .admin setting
operator.createMerge = (opts) => {
  return (opts.admin) ? fullMerge : partialMerge; 
}

// do full merge
operator.merge({admin: true})

// return merge function dependent on whether role of target object (a User) is 'admin'
operator.createMerge = (opts) => {
  return (opts.targetObj.role === 'admin') ? fullMerge : partialMerge; 
}

operator.target = {
  name: 'kris',
  role: 'admin'
}

// do full merge
operator.merge();

operator.target = {
  name: 'sandy',
  role: 'guest'
}

// do partial merge
operator.merge();

The example demo (excluding use of createMerge) can be found in /examples/demo.js in the repo.

Set alternative jsonpath engine

You can now also set an alternative jsonpath engine.

const fastpath = require('fastpath');
operator.jp = fastpath;

Note: Some jsonpath alternatives don't yet have a filter function (for delete) and be fully compatible. You might however be able to add filter (and apply) from jsonpath?

fastpath.filter = jsonpath.prototype.filter;

Advanced usage

Example:

var all = []

fs.createReadStream('./data.json')
  .pipe(split())
  .pipe(through2.obj(function (obj, enc, callback) {
    const operator = new JsonOperator(obj)
    // some JSON operations
    // ...
    let data = operator.result;
    this.push(data)
    callback()
  }))
  .on('data', function (data) {
    all.push(data)
  })
  .on('end', function () {
    doSomethingSpecial(all)
  })

To operate efficiently on a folder with json file perhaps use readdirp to read directory as a stream. See next example for inspiration.

Mongoose streaming example

querystream

const paths = {
  // ...
  userRole: '$.user.role'
};

const jsonTransformStream = new JsonTransformStream({ paths });

Model.find().stream().pipe(jsonTransformStream).pipe(ctx.body);
const Transform = require('stream').Transform

module.exports = class JsonTransformStream extends Transform {
  constructor(options = {}) {
    this.operator = new JsonOperator();
    this.paths = options.paths;
    this.objs = options.objs;
  }

  // do some user role operations!
  operateOn(obj) {
    this.operator.target = chunk;
    if (obj.user) {
      this.operator.merge(this.objs.role, {path: this.paths.userRole})
    }
    this.push(this.operator.result);
  }

  _transform(chunk, encoding, done) {
    this.operateOn(chunk)
    done();
  }

  _flush(done) {
      done();
  }
}

Alternatives

Some alternative jsonpath transformers

Would be nice if we could combine the best of all ;)

JSON path alternative

Misc

Licences

MIT

Kristian Mandrup [email protected] 2016