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

key-value-pointer

v0.7.4

Published

Query and handle JSON and JavaScript objects.

Downloads

164

Readme

Key-Value-Pointer

The KVP library is for query and handle JSON and JavaScript objects with a tree structure. The purpose is to use simple methods that feels very natural for a JavaScript programmer, instead of using some kind of query language that tries to imitate CSS and XML querying.

JavaScript arrays have built-in methods like forEach and map, and simple property objects can be searched by a for(x in obj) {...}, but when the object structure gets deeper and more arbitrary, you need to make special solutions to go through the object. Key-Value-Pointer is a library that can help you with that. You simply make a callback function and pass it to the query method. For each node, your callback gets called with a parameter with the three properties key, value, and pointer. With this information, and a few methods in this library, you can do all kinds of search and transformations in your JavaScript object.

Start by wrap your JavaScript object, or JSON string with the kvp function. The resulting object has a few methods that you can use for easy handling of your object.

query

The query method takes a callback and stop searching when the callback returns true. The parameter passed to the callback is a JavaScript object with the properties key, value, and pointer, where key is a string with the name of the node, value is its value or reference, if it is an object, and pointer is the JSON Pointer string for the node. By evaluating these properties, the callback can decide to stop the search by returning a true value. The return value of the query method is the value of the hit.

// "obj" is a JavaScript object or a JSON structure
var res = kvp(obj).query(function (node) {
	// the node parameter is an object with the properties "key", "value", and "pointer"
	// {
	//   key: 'title',
	//   value: 'Semantic Version',
	//   pointer: '/definitions/semVer/title'
	// }
	if (typeof node.value === 'string' && node.value.match(/tic/)) {
		// if it is a hit, return true
		return true;
	}
});

The query method is a way to traverse all properties inside a JavaScript object, level by level, and apply the callback on each of these properties. Inside the callback all the kvp methods can be accessible with the this keyword, so a lot of things can be done.

It is also possible to make the query method start somewhere inside the object instead of traverse all levels, by passing a JSON Pointer before the callback. Here is an example of an object with a list of values, and a property saying what should be the max value in the list. Our query will start in the list property, but can access the whole object inside the callback, and in this case change all values exceeding the max value.

The result of a query is normally the value of the node where the query run is stopped, but by returning something else than true, the returned value can be changed.

var points = {
	max: 10,
	list: [2, 5, 13, 8, 15, 3, 7]
}

kvp(points).query('/list', function (node) {
    if (node.value > this.select('/max')) {
        this.replace(node.pointer, this.select('/max'));
    }
});

// points === [2, 5, 10, 8, 10, 3, 7]

select

When a JSON Pointer is known, its value can immediately be selected by the select method. Pass the pointer as a parameter.

// the fifth item in the array "d"
var res = kvp(o).select('/d/4');

// push an item to the array "bar"
kvp(o).select('/foo/bar').push(999);
// same as
o['foo']['bar'].push(999);

replace

To replace a value in the object or JSON passed to kvp, use the method replace. It takes two parameters, the JSON Pointer and the new value. It can be used together with the query method, to find where to replace the value.

kvp(obj).query(function (node) {
		if (node.key === 'language') {
			this.replace(node.pointer, 'JavaScript');
			return true;
		}
});
// obj is modified

insert

The insert method inserts a value in the place the pointer points to. If the structure is not there the insert method creates it. An insert is not supposed to replace anything.

remove

To remove a node when the JSON Pointer is known, use the remove method.

kvp(obj).remove('/a/b/c');

getObject and getJSON

Two methods for getting the whole structure out of kvp.

var k = kvp({"ok": true});
var obj = k.getObject();
var json = k.getJSON();

dirname and basename

A JSON Pointer is like a path, and it can be seen as a concatenation of a directory and a name. To conveniently get these out of the pointer string, the two methods dirname and basename can be used.

var pointer = '/usr/var/log';
var dir = kvp.dirname(pointer);
// dir === '/usr/var'
var name = kvp.basename(pointer);
// name === 'log'

apply

The apply method is to prepare transisions of an object with a function and apply the function as a callback when needed. The apply method is chainable, it returns a kvp wrapped object so any other method can be used on it.

function lic () {
  this.insert('/copyright', {
    year: (new Date()).getFullYear(),
    name: 'Foo Bar',
    license: 'MIT'
  });
}

var json = kvp({}).apply(lic).getJSON();

console.log(json);

filter

The filter method traverse the whole object, just like the query method, but instead of return the value when the callback returns true, it continues and include only parts of the object where the callback returns true. The return of the filter method is a kvp wrapped result object, so any method can be chained after the filter method. To stop filtering before the whole object is traversed, do a this.done = true; in the callback, since unlike query the true return in the callback will not stop the traverse. The filter method can have an optional first argument in form of a pointer, to limit the traverse on only a part of the object .filter('/cats', function (node) { ... }). In case of a pointer argument, the callback can be omitted and whatever the pointer points to will be the filtered result.

var obj = kvp({
  dogs: {
      sverre: false,
      tommy: false,
      solo: true,
      meja: true
   },
   cats: {
      ziggy: true,
      kompis: true,
      svinto: true,
      polarn: true
   }
});

var out1 = obj
  .filter(function (node) {
    if (node.key === 'ziggy') this.done = true;
    if (typeof node.value === 'boolean' && node.value) return true;
  });
  
console.log(out1.getObject());
// { dogs: { solo: true, meja: true }, cats: { ziggy: true } }

var out2 = out1.filter('/cats').getObject();

console.log(out2);
// { cats: { ziggy: true } }

Installation

In Node.js:

npm install key-value-pointer

In the browser, download kvp.js and use it with an AMD loader:

require(['kvp'], function (kvp) {
	// ...
})

Or in the browser without a module loader:

<script src="kvp.js"></script>
<script>
  var kvp = window.keyValuePointer;
  // ...
</script>

CouchDB and Cloudant

This library is small enough to be used in design documents in CouchDB and Cloudant. An example of a design document is design_document.json. With kvp the documents can be analyzed better and advanced indexes can be created. Notice the way require is used when importing the library in the function, var kvp = require("views/lib/kvp").kvp;.

Select Many

There is no method for a query with many results, because less is more in libraries like this. It doesn't mean that you can't do it, but you have to make your own function that fit your special need. Here is an example of how you can find all nodes in a JSON structure with a certain key name.

function queryAll (json, name)	{
	var list = [];
	kvp(json).query(function (node) {
			if (node.key === name) {
				list.push(node.value);
			}
		}
	)
	return list;
}

var all_zip_codes = queryAll(json_doc, 'zip'));

In the callback you have the key, the value, and the position in the document in form of a JSON Pointer string for each node in the document. You can do all kinds of calculations on these. If the node is an object, you can actually change it directly, since node.value is a reference to the original object (unless it is a JSON string). Inside the callback function, you can also use the built-in methods like select, replace, and remove on the original object by calling them with "this". So you can copy and move parts of the document to other places in the document.

var k = kvp({"what": "foo"});
var res = k.query(function (node) {
		if (node.key === 'what') {
			this.replace(node.pointer, 'bar');
			return true;
		}
	});
// res === 'bar' and
// k.getObject() === {'what': 'bar'}

Make an Index

Another example. Here we want an index of all nodes directly under all objects with the key name "properties", and later use this index to add a property in the original object.

// run in Node.js
var kvp = require('key-value-pointer'),
    request = require('sync-request'),
    doc = JSON.parse(request('GET', 'https://steenk.github.io/schemas/bacon.json').getBody());

function indexProperties (obj)  {
    var idx = {};
    kvp(obj).query(function (node) {
            if (node.pointer.match(/properties\/[^\/]+$/)) {
                idx[node.key] = node.value;
            }
        }
    )
    return idx;
}

// before change
console.log('Before:', doc.properties.git);

// make an index of propertiy names with reference to their place
var props = indexProperties(doc)
console.log('Found names:', Object.keys(props));

// insert something with the index
if (props['git']) props['git'].type = 'string';

// see how the original object has changed
console.log('After:', doc.properties.git);

A JSON document is fetched from the Internet, and converted to a JavaScript object with JSON.parse. The function returns a list of keys and references to their position in the doc object. After a check that the name "git" exists, a property "type" is added in the doc.

Prevent Deep Search

You have an object that is many levels deep, and you want to limit how deep your search will be. This is how it is done, because the query traverses the object level by level.

kvp(obj).query(function (node) {
	if (node.level < 3) {
		// do your stuff
	} else {
		return true;
	}
});

Replace References

Some documents use references to another places in the document. This function replace references with their real values.

var refs = {
	a: {'$ref': 'a'},
	d: {
		b: {'$ref': 'b'},
		c: {'$ref': 'c'}
	},
	definitions: {
		a: 1000,
		b: 2000,
		c: 3000
	}
}

// pass a json makes it a copy
var doc = kvp(JSON.stringify(refs)).query(function (node) {
	if (node.key === '$ref') {
		var parent = this.dirname(node.pointer);
		var name = this.basename(parent);
		this.replace(parent, this.select('/definitions/' + name))
	}
});

// get the object and delete "definitions"
delete doc.definitions;


// doc === { a: 1000, d: { b: 2000, c: 3000 } }