Skip to content

Repository files navigation

Irrelon Path

A powerful JSON path processor with no third-party dependencies. Allows you to traverse JSON object trees with a simple dot-delimited path format e.g. "obj.name"

What Can It Do?

Irrelon Path is a JavaScript object manipulation library that uses dot notation to denote object key / value field locations within the object structure. It allows you to easily access, modify or remove data from an object at locations specified via a path string.

Install

npm i @irrelon/path

Quick Reference

Simple Usage

const{get}=require("@irrelon/path");// Define an object in JSONconstobj={"users": {"test1": {"name": "My Test User"}}};// Grab data from the object via the path solverconstresult=get(obj,'users.test1.name');console.log(result);// Logs: My Test User

Escaping Fields with Periods

Sometimes you want to access data where a field name has periods in it like this:

constobj={"users": {"test@test.com": {"name": "My Test User"}}};

The user email address "test@test.com" contains a period that the path solver will interpret as a traversal indicator. If we try to ask the path solver to get the data in the key "test@test.com" it will look for a field called "test@test" with a sub-field "com".

To avoid this, escape the period using the escape() function:

const{get, escape}=require("@irrelon/path");constresult=get(obj,`users.${escape('test@test.com')}.name`);console.log(result);// Logs: My Test User

Behaviour

If data or an object to traverse does not exist inside the base object, the path solver will return undefined and will NOT throw an error:

const{get}=require("@irrelon/path");constobj={"foo": null};constresult=get(obj,"foo.bar.one");console.log(result);// Logs: undefined

Default Values

When using get() you can specify a default value to return if the value at the given path is undefined.

const{get}=require("@irrelon/path");constobj={"foo": null};constresult=get(obj,"foo.bar.one","My Default Value");console.log(result);// Logs: My Default Value

Methods

chop (path, level)

ParamTypeRequiredDefault
pathStringtruenone
levelNumbertruenone

Chops a path string down to the given level. Given a path string like "foo.bar.ram.you.too", chop will remove any path parts below the given level. If we pass 2 as the level with that given path, the result will be "foo.bar" as foo is level 1 and bar is level 2.

If the path is shorter than the given level, it is returned intact.

const{chop}=require("@irrelon/path");constresult=chop("foo.bar.one",2);console.log(result);// Logs: foo.bar

clean (path)

ParamTypeRequiredDefault
pathStringtruenone

Removes leading period (.) from string and returns new string.

const{clean}=require("@irrelon/path");constresult=clean(".foo.bar.one");console.log(result);// Logs: foo.bar.one

countLeafNodes (obj)

ParamTypeRequiredDefault
objObject or Arraytruenone

Counts the total number of key leaf nodes in the passed object. Leaf nodes are any key that does not have a value of object or array.

const{countLeafNodes}=require("@irrelon/path");constresult=countLeafNodes({"foo": {"bar": null},"moo": true});console.log(result);// Logs: 2

countMatchingPathsInObject (testKeys, testObj)

ParamTypeRequiredDefault
testKeysObject or Arraytruenone
testObjObject or Arraytruenone

Tests if the passed object has the paths that are specified and that a value exists in those paths and if so returns the number matched. The output includes matchedKeys with an object where the same structure exists as the testObj where each leaf node key will be a boolean that describes if the leaf node exists in the testKeys object.

MAY NOT BE INFINITE RECURSION SAFE.

const{countMatchingPathsInObject}=require("@irrelon/path");constresult=countMatchingPathsInObject({"moo": true},{"foo": {"bar": null},"moo": true});console.log(result);

Outputs:

{
"matchedKeyCount": 1,
"matchedKeys": {
"foo": {
"bar": false
},
"moo": true
},
"totalKeyCount": 2
}

decouple (obj, options = {})

ParamTypeRequiredDefault
objObject or Arraytruenone
optionsObjectfalse{}
options.immutableBooleanfalsefalse

If options.immutable === true then return a new de-referenced instance of the passed object/array. If immutable is false then simply return the same obj that was passed. The returned instance is NOT deeply immutably cloned because we recurse through object trees and only immutably clone when making changes. This is useful so you can instantly compare two states with a strict equality check such as when using setImmutable().

const{decouple}=require("@irrelon/path");constobj={"foo": true};constresult=decouple(obj);console.log(result);console.log(result===obj);

Outputs:

{
"foo": true
}
false

diff (obj1, obj2, options = {})

ParamTypeRequiredDefault
obj1Object or Arraytruenone
obj2Object or Arraytruenone
optionsObjectfalse{}
options.strictBooleanfalsefalse
options.maxDepthNumberfalseInfinity
options.basePathStringfalse""
options.exclusiveBooleanfalsefalse

If you need to know the description of the differences rather than only the paths that contain differences, use diffValues() instead.

Compares two objects / arrays and returns the differences as an array of paths to the different fields.

Fields are considered "different" if they do not contain equal values. The equality check is either strict or non-strict based on the options.strict argument.

It is important to understand that this function detects differences between field values, not differences between object structures. For instance if a field in obj1 contains undefined and obj2 does not contain that field at all, it's value in obj2 will also technically be equal to undefined so there would be no difference detected.

const{diff}=require("@irrelon/path");constobj1={"user": {"_id": 1,"firstName": "Jimbo","lastName": "Jetson"}};constobj2={"user": {"_id": "1",// Notice string instead of numerical _id"firstName": "James",// We also changed the name from "Jimbo" to "James""lastName": "Jetson"}};constresultArr1=diff(obj1,obj2,{basePath: "",strict: false});// Non-strict equality checkconstresultArr2=diff(obj1,obj2,{basePath: "",strict: true});// Strict equality checkconsole.log(resultArr1);// Logs: ["user.firstName"]console.log(resultArr2);// Logs: ["user._id", "user.firstName"]

diffValues (obj1, obj2, path, strict, maxDepth)

ParamTypeRequiredDefault
obj1Object or Arraytruenone
obj2Object or Arraytruenone
optionsObjectfalse{}
options.strictBooleanfalsefalse
options.maxDepthNumberfalseInfinity
options.basePathStringfalse""
options.exclusiveBooleanfalsefalse

Compares two objects / arrays and returns the differences as an object where keys are paths to values that differ and the values are objects describing the difference, including the values and types.

Fields are considered "different" if they do not contain equal values. The equality check is either strict or non-strict based on the options.strict argument.

It is important to understand that this function detects differences between field values, not differences between object structures. For instance if a field in obj1 contains undefined and obj2 does not contain that field at all, it's value in obj2 will also technically be equal to undefined so there would be no difference detected.

const{diffValues}=require("@irrelon/path");constobj1={"user": {"_id": 1,"firstName": "Jimbo","lastName": "Jetson"}};constobj2={"user": {"_id": "1",// Notice string instead of numerical _id"firstName": "James",// We also changed the name from "Jimbo" to "James""lastName": "Jetson"}};constresultArr1=diffValues(obj1,obj2,{basePath: "",strict: false});// Non-strict equality checkconstresultArr2=diffValues(obj1,obj2,{basePath: "",strict: true});// Strict equality checkconsole.log(resultArr1);// Logs: {"user.firstName": {val1: "Jimbo", val2: "James", type1: "string", type2: "string", difference: "value"}}console.log(resultArr2);/*	Logs: 	{ "user._id": { val1: 1, val2: "1", type1: "number", type2: "string", difference: "type" }, "user.firstName": { val1: "Jimbo", val2: "James", type1: "string", type2: "string", difference: "value" }	} */

distill (obj, pathArr)

ParamTypeRequiredDefault
objObject or Arraytruenone
pathArrArraytruenone

Gets the values of the paths in pathArr and returns them as an object with each key matching the path and the value matching the value from obj that was at that path.

const{distill}=require("@irrelon/path");constobj={"user": {"firstName": "Jim","lastName": "Jones","age": 22}};constresult=distill(obj,["user.firstName","user.lastName"]);console.log(result);

Outputs:

{
"user.firstName": "Jim",
"user.lastName": "Jones"
}

down (path, levels = 1)

ParamTypeRequiredDefault
pathStringtruenone
levelsNumberfalse1

Returns the given path after removing the first leaf from the path. E.g. "foo.bar.thing" becomes "bar.thing".

const{down}=require("@irrelon/path");constresult=down("user.friends.0.firstName");console.log(result);

Outputs:

"friends.0.firstName"

See also up(), pop(), shift()

escape (path)

ParamTypeRequiredDefault
pathStringtruenone

Escapes any periods in the passed string so they will not be identified as part of a path. Useful if you have a path like "domains.www.google.com.data" where the "www.google.com" should not be considered part of the traversal as it is actually in an object like:

{
"domains": {
"www.google.com": {
"data": "foo"
}
}
}

Usage:

const{escape}=require("@irrelon/path");constresult=escape("www.google.com");console.log(result);

Outputs:

"www\\.google\\.com"

findOnePath (source, query)

Finds the first item that matches the structure of query and returns the path to it

const{findOnePath}=require("@irrelon/path");constmyDataArray=[{"profile": {"id": 1,"name": "Ron Swanson"}},{"profile": {"id": 2,"name": "April Ludgate"}}];// Find the object that has a key "profile"// with a object that has a key "_id" that // has a value 1, and return the path to itconstresult1=findOnePath(myDataArray,{profile: {_id: 1}});console.log(result1);// Logs: "0"// Find the object that has a key "_id" that // has a value 1, and return the path to itconstresult2=findOnePath(myDataArray,{_id: 1});console.log(result2);// Logs: "0.profile"

See the unit tests for findOnePath() for many more examples of usage.

findPath (source, query)

ParamTypeRequiredDefaultDescription
source*truenoneThe source to test.
query*truenoneThe query to match.

Finds all items in source that match the structure of query and returns the path to them as an array of strings.

const{findPath}=require("@irrelon/path");constmyData={"profile": {"id": 1,"name": "Ron Swanson","data": {"mobile": "+001293284732"}}};constresult=findPath(myData,{data: {"mobile": "+001293284732"}});console.log(result);

Output:

{
"match": true,
"path": [
"profile"
]
}

flatten (obj)

ParamTypeRequiredDefaultDescription
objObject or ArraytruenoneThe object to scan.

Takes an object and finds all paths, then returns the paths as an array of strings.

const{flatten}=require("@irrelon/path");constmyData={"profile": {"id": 1,"name": "Ron Swanson"}};constresult=flatten(myData);console.log(result);

Output:

[
"profile.id",
"profile.name",
"profile"
]

flattenValues (obj)

ParamTypeRequiredDefaultDescription
objObject or ArraytruenoneThe object to scan.

Takes an object and finds all paths, then returns the paths as keys and the values of each path as the values.

const{flattenValues}=require("@irrelon/path");constmyData={"profile": {"id": 1,"name": "Ron Swanson"}};constresult=flattenValues(myData);console.log(result);

Output:

{
"profile": {
"id": 1,
"name": "Ron Swanson"
},
"profile.id": 1,
"profile.name": "Ron Swanson"
}

furthest (obj, path)

ParamTypeRequiredDefaultDescription
objObject or ArraytruenoneThe object to operate on.
pathStringtruenoneThe object to operate on.

Given object obj and a path, determines the outermost leaf node that can be reached where the leaf value is not undefined.

const{furthest}=require("@irrelon/path");constmyData={"profile": {"id": 1,"name": "Ron Swanson"}};constresult=furthest(myData,"profile.id.bson");console.log(result);

Output:

"profile.id"

get (obj, path, defaultValue)

ParamTypeRequiredDefault
objObject or Arraytruenone
pathStringtruenone
defaultValueAnyfalseundefined

Gets a value from the obj at the given path and if no value exists for that path, returns defaultValue if one was provided.

const{get}=require("@irrelon/path");constobj={"foo": null};constresult1=get(obj,"foo");constresult2=get(obj,"foo.bar.one","My Default Value");console.log(result1);// Logs: nullconsole.log(result2);// Logs: My Default Value

If you want to access elements of an array, simply use the element index as part of your path e.g.

const{get}=require("@irrelon/path");constobj={"myArr": ["hello",{"bar": "goodbye"}]};constresult1=get(obj,"myArr.0");// helloconstresult2=get(obj,"myArr.1.bar");// goodbye

set (obj, path, value)

ParamTypeRequiredDefault
objObject or Arraytruenone
pathStringtruenone
valueAnytruenone

Sets a value in the obj at the given path.

If the given path doesn't exist in the target object it will be created by making each non-existent path part a new object.

const{set, get}=require("@irrelon/path");constobj={"foo": null};constresult1=get(obj,"foo.bar");// Currently: undefinedset(obj,"foo.bar","hello");constresult2=get(obj,"foo.bar");console.log(result1);// Logs: undefinedconsole.log(result2);// Logs: hello

setImmutable (obj, path, value)

This is a helper function that calls set() with immutable flag switched on.

ParamTypeRequiredDefault
objObject or Arraytruenone
pathStringtruenone
valueAnytruenone

Sets a value in the obj at the given path in an immutable way and returns a new object. This will not change or modify the existing obj.

Keep in mind that references to objects that were not modified by the operation remain the same. This allows systems like React to appropriately act on changes to specific data rather than re-rendering an entire DOM tree when one sub-object changes.

If the given path doesn't exist in the target object it will be created by making each non-existent path part a new object.

const{setImmutable, get}=require("@irrelon/path");constobj={"foo": {"bar": "goodbye","subBar": {"somethingElse": true}},"otherObj": {"enabled": true}};constresult1=get(obj,"foo.bar");// Currently: goodbyeconstnewObj=setImmutable(obj,"foo.bar","hello");// Original object remains unmodified (will still be "goodbye");constresult2=get(obj,"foo.bar");// New object has new value of "hello"constresult3=get(newObj,"foo.bar");console.log(result1);// Logs: goodbyeconsole.log(result2);// Logs: goodbyeconsole.log(result3);// Logs: hello// Objects that did not have any modifications remain the same// and still share a reference in memoryconsole.log(obj.otherObj===newObj.otherObj);// Logs: true// Objects that did have modifications will not be the sameconsole.log(obj.foo===newObj.foo);// Logs: false// Child objects of modified parents will still have references// to the original since the child object wasn't modified directlyconsole.log(obj.foo.subBar===newObj.foo.subBar);// Logs: true

unSet (obj, path)

ParamTypeRequiredDefault
objObject or Arraytruenone
pathStringtruenone

Deletes a key from an object by the given path.

constobj={"foo": {"bar": [{"moo": true,"baa": "ram you"}]}};console.log(obj.foo.bar[0].baa);// Logs: ram youunSet(obj,"foo.bar.0.baa");console.log(obj.foo.bar[0].baa);// Logs: undefinedconsole.log(obj.foo.bar[0].hasOwnProperty("baa"));// Logs: false

update (obj, basePath, updateData, options = {})

ParamTypeRequiredDefault
objObject or Arraytruenone
basePathStringtrue""
updateDataObject or Arraytruenone
optionsObjectfalse{}
options.immutableBooleanfalsefalse
options.strictBooleanfalsefalse
options.ignoreRegExpfalsefalse

Sets a single value on the passed object and given path. This will directly modify the "obj" object.

If you need immutable updates, use updateImmutable() instead.

constobj={"foo": {"bar": [{"moo": true,"baa": "ram you"}]}};console.log(obj.foo.bar[0].baa);// Logs: ram you// Calling this function with a basePath as an empty string// will operate directly on the passed `obj` instead of a // sub-object of `obj`.update(obj,"",{"foo.bar.0.baa": "hello I've been updated","and.so": "have I!"});console.log(obj.foo.bar[0].baa);// Logs: hello I've been updatedconsole.log(obj.and.so);// Logs: have I!

Version 5.x Breaking Changes

The update() and updateImmutable() functions have their signature changed to include a base path in the arguments. If migrating from a previous version you can simply add an empty string as the basePath argument to have the functions operate in the same way as before e.g.

Before Version 5.x

update(obj,updateObj);updateImmutable(obj,updateObj);

After Version 5.x

update(obj,"",updateObj);updateImmutable(obj,"",updateObj);

The basePath argument was added so that you can target a path within the passed obj to receive the update e.g.

constobj={subObj: {}};update(obj,"subObj",{"foo": true});

The update above will modify obj.subObj.foo to equal true.

Version 3.x Breaking Changes

There was a bug in the get() function that would return an incorrect value when a non-object was passed to get data from and a path was passed e.g.

get("foo-im-not-an-object","some.path.to.get.data.from");// Version 2.x returned "foo-im-not-an-object"

In version 3.x, this call will return undefined as expected.

Version 2.x Breaking Changes

Version 1.x exported a class that you could instantiate. Version 2.x exports an object with all available functions. You can require version 2.x either all at once (all functions) or you can destructure to require only the functions you need. This change is primarily to support tree shaking, as well as move to a more functional programming style, albeit not pure functional style :)

Version 2.x is a breaking change from version 1.x and you will need to migrate your code to work with the new version. Migration is fairly simple and instead of using an instance of the 1.x class, you simply require the parts of the library you need e.g.

Version 1.x Style Code (Don't Do This)

// DON'T DO THIS !!!!!!!!!!!constPath=require("irrelon-path");constpathSolver=newPath();consta={hello: {foo: true}};constb=pathSolver.get(a,"hello.foo");// b === true

Version 2.x Style Code (Please Use This)

// DO THIS :)const{get}=require("@irrelon/path");consta={hello: {foo: true}};constb=get(a,"hello.foo");// b === true

About

A powerful JSON path processor. Allows you to drill into JSON objects with a simple dot-delimited path format e.g. "obj.name"

Resources

Code of conduct

Contributing

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages