JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 15819
  • Score
    100M100P100Q137985F
  • License MIT

Flexible and performant utility for traversing javascript objects

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


Flexible and performant utility for traversing javascript objects.

Installation

npm i object-traversal

✔ Features

  1. Performance
    • Traverses over 20 million nodes per second. (2020 MacBook Air)
    • Around 10 times faster than popular alternatives. (npm run benchmark)
  2. Configurable
    • Tweak traversalOpts for even more speed, traversal order, maxDepth and more.
  3. Zero dependencies
    • Works on both NodeJS and the browser.
  4. Big test coverage
  5. 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'

Built with

TSDX
np
yarn 1.22.10