Package Exports
- object-traversal
- object-traversal/dist/index.js
- object-traversal/dist/object-traversal.esm.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (object-traversal) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
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
Click to expand
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
Click to expand
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
Click to expand
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
Click to expand
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
Click to expand
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
- Configurable BFS/DFS
- Max depth
- Configurable path separator
- Utility for consuming paths
- Toggleable cycle handler
- Iterator support
- Sequential promise support
- Multi threading & further speed enhancements
- Streaming research
- More granular cycleHandling: 'revisit', 'norevisit', and 'off'