Flexible and performant utility for traversing javascript objects
npm i object-traversal
1. Performance
- Traverses over 20 million nodes per second. _(2020 MacBook Air)_
- Around 10 times faster than popular alternatives. _(npm run benchmark)_
1. Configurable
- Tweak traversalOpts for even more speed, traversal order, maxDepth and more.
1. Zero dependencies
- Works on both NodeJS and the browser.
1. Big test coverage
1. Typescript
``javascript
import { traverse } from 'object-traversal';
traverse(object, callback, opts?);
`
Any instance of javascript object, cyclic or otherwise.
A function that will be called once for each node in the provided root object, including the root object itself.
The callback function has the following signature:
`typescript
// Callback function signature
export type TraversalCallback = (context: TraversalCallbackContext) => any;
// Callback context
export type TraversalCallbackContext = {
parent: ArbitraryObject | null; // parent is null when callback is being called on the root objectobject
key: string | null; // key is null when callback is being called on the root `
value: any;
meta: {
nodePath?: string | null;
visitedNodes: WeakSet
depth: number;
};
};
An optional configuration object. See below for the available options and their default values.
`typescript
export type TraversalOpts = {
/**
* Default: 'depth-first'
*/
traversalType?: 'depth-first' | 'breadth-first';
/**
* Traversal stops when the traversed node count reaches this value.
*
* Default: Number.Infinity
*/
maxNodes?: number;
/**
* If set to true, prevents infinite loops by not re-visiting repeated nodes.
*
* Default: true
*/
cycleHandling?: boolean;
/**
* The maximum depth that must be traversed.
*
* Root object has depth 0.
*
* Default: Number.Infinity
*/
maxDepth?: number;
/**
* If true, traversal will stop as soon as the callback returns a truthy value.
*
* This is useful for search use cases, where you typically want to skip traversing the remaining nodes once the target is found.
*
* Default: false
*/
haltOnTruthy?: boolean;
/**
* The string to be used as separator for the meta.nodePath segments.meta.nodePath
*
* Set to null if you wish to turn off to increase traversal speed.`
*
* Default: '.'
*/
pathSeparator?: string | null;
};
Click to expand
`javascript`
exampleObject = {
name: 'Hello World!',
age: 1,
accounts: 2,
friends: 3,
};
`javascript
function double({ parent, key, value, meta }) {
if (typeof value === 'number') {
parent[key] = value * 2;
}
}
traverse(exampleObject, double);
console.log(exampleObject);
// {
// name: 'Hello World!',
// age: 2,
// accounts: { checking: 4, savings: 6 },
// friends: 8
// }
`
Click to expand
`javascript`
network = {
name: 'Person1',
age: 52,
friends: [
{
name: 'Person2',
age: 25,
friends: [],
},
{
name: 'Person3',
age: 42,
friends: [
{
name: 'Person4',
age: 18,
friends: [
{
name: 'Person5',
age: 33,
friends: [],
},
],
},
],
},
],
};
`javascript
const numbersOver25 = [];
function collectOver25({ parent, key, value, meta }) {
if (key === 'age' && value > 25) {
numbersOver25.push(value);
}
}
traverse(network, collectOver25);
console.log(numbersOver25);
// [ 52, 42, 33 ]
`
Click to expand
`javascript`
network = {
name: 'Alice Doe',
age: 52,
friends: [
{
name: 'John Doe',
age: 25,
friends: [],
},
{
name: 'Bob Doe',
age: 42,
friends: [
{
name: 'John Smith',
age: 18,
friends: [
{
name: 'Charlie Doe',
age: 33,
friends: [],
},
],
},
],
},
],
};
`javascript
const pathsToPeopleNamedJohn = [];
function callback({ parent, key, value, meta }) {
if (value.name && value.name.startsWith('John')) {
pathsToPeopleNamedJohn.push(meta.nodePath);
}
}
traverse(network, callback);
console.log(pathsToPeopleNamedJohn);
// [ 'friends.0', 'friends.1.friends.0' ]
`
Click to expand
`javascript`
network = {
name: 'Alice Doe',
age: 52,
friends: [
{
name: 'John Doe',
age: 25,
friends: [],
},
{
name: 'Bob Doe',
age: 42,
friends: [
{
name: 'John Smith',
age: 18,
friends: [
{
name: 'Charlie Doe',
age: 33,
friends: [],
},
],
},
],
},
],
};
`javascript
import { getNodeByPath } from 'object-traversal';
const firstFriend = getNodeByPath(network, 'friends.0');
console.log(firstFriend);
// { name: 'John Doe', age: 25, friends: [] }
`
Click to expand
`javascript`
network = {
name: 'Person 1',
age: 52,
friends: [
{
name: 'Person 2',
age: 42,
friends: [
{
name: 'Person 4',
age: 18,
friends: [],
},
],
},
{
name: 'Person 3',
age: 25,
friends: [],
},
],
};
`javascript
let names = [];
function getName({ parent, key, value, meta }) {
if (value.name) {
names.push(value.name);
}
}
traverse(network, getName, { traversalType: 'breadth-first' });
console.log(names);
// [ 'Person 1', 'Person 2', 'Person 3', 'Person 4' ]
``
- [x] Configurable BFS/DFS
- [x] Max depth
- [x] Configurable path separator
- [x] Utility for consuming paths
- [x] Toggleable cycle handler
- [ ] Iterator support
- [ ] Sequential promise support
- [ ] Multi threading & further speed enhancements
- [ ] Streaming research
- [ ] More granular cycleHandling: 'revisit', 'norevisit', and 'off'