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 @erboladaiorg/magnam-tempora
Quick Reference
Simple Usage
const {get} = require("@erboladaiorg/magnam-tempora");
const obj = {
"users": {
"test1": {
"name": "My Test User"
}
}
};
const result = get(obj, 'users.test1.name');
console.log(result);
Escaping Fields with Periods
Sometimes you want to access data where a field name has periods in it like this:
const obj = {
"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("@erboladaiorg/magnam-tempora");
const result = get(obj, `users.${escape('test@test.com')}.name`);
console.log(result);
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("@erboladaiorg/magnam-tempora");
const obj = {
"foo": null
};
const result = get(obj, "foo.bar.one");
console.log(result);
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("@erboladaiorg/magnam-tempora");
const obj = {
"foo": null
};
const result = get(obj, "foo.bar.one", "My Default Value");
console.log(result);
Methods
chop (path
, level
)
Param | Type | Required | Default |
---|
path | String | true | none |
level | Number | true | none |
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("@erboladaiorg/magnam-tempora");
const result = chop("foo.bar.one", 2);
console.log(result);
clean (path
)
Param | Type | Required | Default |
---|
path | String | true | none |
Removes leading period (.) from string and returns new string.
const {clean} = require("@erboladaiorg/magnam-tempora");
const result = clean(".foo.bar.one");
console.log(result);
countLeafNodes (obj
)
Param | Type | Required | Default |
---|
obj | Object or Array | true | none |
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("@erboladaiorg/magnam-tempora");
const result = countLeafNodes({"foo": {"bar": null}, "moo": true});
console.log(result);
countMatchingPathsInObject (testKeys
, testObj
)
Param | Type | Required | Default |
---|
testKeys | Object or Array | true | none |
testObj | Object or Array | true | none |
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("@erboladaiorg/magnam-tempora");
const result = 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
= {})
Param | Type | Required | Default |
---|
obj | Object or Array | true | none |
options | Object | false | {} |
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("@erboladaiorg/magnam-tempora");
const obj = {"foo": true};
const result = decouple(obj);
console.log(result);
console.log(result === obj);
Outputs:
{"foo": true}
false
diff (obj1
, obj2
, path
, strict
, maxDepth
)
Param | Type | Required | Default |
---|
obj1 | Object or Array | true | none |
obj2 | Object or Array | true | none |
path | String | false | "" |
strict | Boolean | false | false |
maxDepth | Number | false | Infinity |
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 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 be undefined
so there
would be no difference detected.
const {diff} = require("@erboladaiorg/magnam-tempora");
const obj1 = {
"user": {
"_id": 1,
"firstName": "Jimbo",
"lastName": "Jetson"
}
};
const obj2 = {
"user": {
"_id": "1",
"firstName": "James",
"lastName": "Jetson"
}
};
const resultArr1 = diff(obj1, obj2, "", false);
const resultArr2 = diff(obj1, obj2, "", true);
console.log(resultArr1);
console.log(resultArr2);
distill (obj
, pathArr
)
Param | Type | Required | Default |
---|
obj | Object or Array | true | none |
pathArr | Array | true | none |
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("@erboladaiorg/magnam-tempora");
const obj = {
"user": {
"firstName": "Jim",
"lastName": "Jones",
"age": 22
}
};
const result = distill(obj, [
"user.firstName",
"user.lastName"
]);
console.log(result);
Outputs:
{
"user.firstName": "Jim",
"user.lastName": "Jones"
}
down (path
, levels
= 1)
Param | Type | Required | Default |
---|
path | String | true | none |
levels | Number | false | 1 |
Returns the given path after removing the first leaf from the
path. E.g. "foo.bar.thing" becomes "bar.thing".
const {down} = require("@erboladaiorg/magnam-tempora");
const result = down("user.friends.0.firstName");
console.log(result);
Outputs:
"friends.0.firstName"
See also up(), pop(), shift()
escape (path
)
Param | Type | Required | Default |
---|
path | String | true | none |
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("@erboladaiorg/magnam-tempora");
const result = 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("@erboladaiorg/magnam-tempora");
const myDataArray = [{
"profile": {
"id": 1,
"name": "Ron Swanson"
}
}, {
"profile": {
"id": 2,
"name": "April Ludgate"
}
}];
const result1 = findOnePath(myDataArray, {
profile: {
_id: 1
}
});
console.log(result1);
const result2 = findOnePath(myDataArray, {
_id: 1
});
console.log(result2);
See the unit tests for findOnePath() for many more examples
of usage.
findPath (source
, query
)
Param | Type | Required | Default | Description |
---|
source | * | true | none | The source to test. |
query | * | true | none | The 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("@erboladaiorg/magnam-tempora");
const myData = {
"profile": {
"id": 1,
"name": "Ron Swanson",
"data": {
"mobile": "+001293284732"
}
}
};
const result = findPath(myData, {
data: {
"mobile": "+001293284732"
}
});
console.log(result);
Output:
{"match": true, "path": ["profile"]}
flatten (obj
)
Param | Type | Required | Default | Description |
---|
obj | Object or Array | true | none | The object to scan. |
Takes an object and finds all paths, then returns the paths as an array
of strings.
const {flatten} = require("@erboladaiorg/magnam-tempora");
const myData = {
"profile": {
"id": 1,
"name": "Ron Swanson"
}
};
const result = flatten(myData);
console.log(result);
Output:
["profile.id", "profile.name", "profile"]
flattenValues (obj
)
Param | Type | Required | Default | Description |
---|
obj | Object or Array | true | none | The 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("@erboladaiorg/magnam-tempora");
const myData = {
"profile": {
"id": 1,
"name": "Ron Swanson"
}
};
const result = flattenValues(myData);
console.log(result);
Output:
{
"profile": {
"id": 1,
"name": "Ron Swanson"
},
"profile.id": 1,
"profile.name": "Ron Swanson"
}
furthest (obj
, path
)
Param | Type | Required | Default | Description |
---|
obj | Object or Array | true | none | The object to operate on. |
path | String | true | none | The 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("@erboladaiorg/magnam-tempora");
const myData = {
"profile": {
"id": 1,
"name": "Ron Swanson"
}
};
const result = furthest(myData, "profile.id.bson");
console.log(result);
Output:
"profile.id"
get (obj
, path
, defaultValue
)
Param | Type | Required | Default |
---|
obj | Object or Array | true | none |
path | String | true | none |
defaultValue | Any | false | undefined |
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("@erboladaiorg/magnam-tempora");
const obj = {
"foo": null
};
const result1 = get(obj, "foo");
const result2 = get(obj, "foo.bar.one", "My Default Value");
console.log(result1);
console.log(result2);
If you want to access elements of an array, simply use the element index
as part of your path e.g.
const {get} = require("@erboladaiorg/magnam-tempora");
const obj = {
"myArr": [
"hello",
{
"bar": "goodbye"
}
]
};
const result1 = get(obj, "myArr.0");
const result2 = get(obj, "myArr.1.bar");
set (obj
, path
, value
)
Param | Type | Required | Default |
---|
obj | Object or Array | true | none |
path | String | true | none |
value | Any | true | none |
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("@erboladaiorg/magnam-tempora");
const obj = {
"foo": null
};
const result1 = get(obj, "foo.bar");
set(obj, "foo.bar", "hello");
const result2 = get(obj, "foo.bar");
console.log(result1);
console.log(result2);
setImmutable (obj
, path
, value
)
This is a helper function that calls set()
with immutable
flag switched on.
Param | Type | Required | Default |
---|
obj | Object or Array | true | none |
path | String | true | none |
value | Any | true | none |
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("@erboladaiorg/magnam-tempora");
const obj = {
"foo": {
"bar": "goodbye",
"subBar": {
"somethingElse": true
}
},
"otherObj": {
"enabled": true
}
};
const result1 = get(obj, "foo.bar");
const newObj = setImmutable(obj, "foo.bar", "hello");
const result2 = get(obj, "foo.bar");
const result3 = get(newObj, "foo.bar");
console.log(result1);
console.log(result2);
console.log(result3);
console.log(obj.otherObj === newObj.otherObj);
console.log(obj.foo === newObj.foo);
console.log(obj.foo.subBar === newObj.foo.subBar);
unSet (obj
, path
)
Deletes a key from an object by the given path.
const obj = {
"foo": {
"bar": [{
"moo": true,
"baa": "ram you"
}]
}
};
console.log(obj.foo.bar[0].baa);
unSet(obj, "foo.bar.0.baa");
console.log(obj.foo.bar[0].baa);
console.log(obj.foo.bar[0].hasOwnProperty("baa"));
update (obj
, basePath
, updateData
, options
)
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.
const obj = {
"foo": {
"bar": [{
"moo": true,
"baa": "ram you"
}]
}
};
console.log(obj.foo.bar[0].baa);
update(obj, "", {
"foo.bar.0.baa": "hello I've been updated",
"and.so": "have I!"
});
console.log(obj.foo.bar[0].baa);
console.log(obj.and.so);
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.
const obj = {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");
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)
const Path = require("irrelon-path");
const pathSolver = new Path();
const a = {hello: {foo: true}};
const b = pathSolver.get(a, "hello.foo");
Version 2.x Style Code (Please Use This)
const {get} = require("@erboladaiorg/magnam-tempora");
const a = {hello: {foo: true}};
const b = get(a, "hello.foo");