object-traversal
v1.0.1
Published
Flexible and performant utility for traversing javascript objects
Downloads
45,294
Maintainers
Readme
object-traversal
Installation
npm i object-traversal
✔ Features
- Performance
- Traverses over 20 million nodes per second. (2020 MacBook Air)
- Around 10 times faster than popular alternatives. (
npm run benchmark
)
- Configurable
- Tweak
traversalOpts
for even more speed, traversal order, maxDepth and more.
- Tweak
- Zero dependencies
- Works on both NodeJS and the browser.
- Big test coverage
- Typescript
Docs
Usage
import { traverse } from 'object-traversal';
traverse(object, callback, opts?);
object
Any instance of javascript object, cyclic or otherwise.
callback
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:
// 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 `object`
key: string | null; // key is null when callback is being called on the root `object`
value: any;
meta: {
nodePath?: string | null;
visitedNodes: WeakSet<ArbitraryObject>;
depth: number;
};
};
opts
An optional configuration object. See below for the available options and their default values.
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.
*
* Set to null if you wish to turn off `meta.nodePath` to increase traversal speed.
*
* Default: '.'
*/
pathSeparator?: string | null;
};
Examples
Double all numbers in-place
exampleObject = {
name: 'Hello World!',
age: 1,
accounts: 2,
friends: 3,
};
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
// }
Find deep nested values by criteria
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: [],
},
],
},
],
},
],
};
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 ]
Find paths by criteria
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: [],
},
],
},
],
},
],
};
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' ]
Get node by path
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: [],
},
],
},
],
},
],
};
import { getNodeByPath } from 'object-traversal';
const firstFriend = getNodeByPath(network, 'friends.0');
console.log(firstFriend);
// { name: 'John Doe', age: 25, friends: [] }
Breadth-first traversal
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: [],
},
],
};
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' ]
Roadmap
- [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'