walkjs

A Javascript library for traversing object trees.

  • walkjs

Downloads in past

Stats

StarsIssuesVersionUpdatedCreatedSize
walkjs
3.2.4a year ago7 years agoMinified + gzip package size for walkjs in KB

Readme

Contents
  1. Description
  2. Installation
  3. Quickstart
- [Async](#async)
  1. Reference
- [Halting the walk](#halting-the-walk)
- [Configuration](#configuration)
    - [Defaults](#config-defaults)
    - [Builder](#using-the-builder)
- [Callbacks](#callbacks)
- [Nodes](#nodes)
  1. Extra functions
- [Apply](#apply)
- [Deep copy](#deep-copy)
- [Compare](#compare)
- [Reduce](#reduce)
  1. Running walk as a generator
Description
Walk is a Javascript library for traversing object trees. The library includes:
  • Functions for recursive processing of nested object trees and directed graphs
  • User defined, type-specific (array/obj/value) callback hooks that get executed during the traversal
  • Support for asynchronous callbacks, run either in sequence or in parallel
  • Incremental graph traversal through generators, with full async support
  • A variety of convenience functions, which double as implementation examples for the library
Installation
npm install walkjs --save
Quickstart
Below is a simple example of usage, in which we execute a single callback for each node in the object graph. The callback simply prints metadata about the node. We also add a global filter to exclude any nodes whose value is equal to 1.
import { WalkBuilder } from 'walkjs';

const obj = {
    'a': 1,
    'b': [2, 3],
    'c': {'d': 5}
}

function printNode(node: WalkNode){
    console.log("obj" + node.getPath(), "=", node.val)
}

new WalkBuilder()
    .withSimpleCallback(printNode)
    .withGlobalFilter(node => node.val !== 1)
    .walk(obj)

outputs:
obj = { a: 1, b: [ 2, 3 ], c: { d: 4 } }
obj["b"] = [ 2, 3 ]
obj["b"][0] = 2
obj["b"][1] = 3
obj["c"] = { d: 4 }
obj["c"]["d"] = 4

Async

Async walks work almost exactly the same as the sync ones, but have an async signature. All callbacks will be awaited, and therefore still run in sequence. For the async versions below, callback functions may either return Promise<void> or void;
import {AsyncWalkBuilder} from 'walkjs';

const obj = {
    //...
}

async function callApi(node: WalkNode): Promise<void> {
    // do some async work here
}

await new AsyncWalkBuilder()
    .withSimpleCallback(callApi)
    .walk(exampleObject)

See the reference for more details!
Reference
walk(target: any, config: Config<Callback>): void
The primary method for traversing an object and injecting callbacks into the traversal.
walkAsync(target: any, config: Config<AsyncCallback>): Promise<void>
Async version of walk which returns a promise.

Halting the walk

import {apply, Break} from "walkjs";

// the walk will not process for any nodes after this
apply({}, () => throw new Break())

Throwing an instance of this class within a callback will halt processing completely. This allows for early exit, usually for circular graphs or in cases when you no longer need to continue.

Configuration:

  • rootObjectCallbacks: boolean: Ignore callbacks for root objects.
  • parallelizeAsyncCallbacks: boolean: (Only applies to async variations). Ignore executionOrder and run all async callbacks in parallel. Callbacks will still be grouped by position, so this will only apply to callbacks in the same position group.
  • runCallbacks: boolean: Set this to false to skip callbacks completely.
  • callbacks: Callback<T>[]: an array of callback objects. See the Callback section for more information.
  • traversalMode: 'depth'|'breadth': the mode for traversing the tree. Options are depth for depth-first
processing and breadth for breadth-first processing.
  • graphMode: 'finiteTree'|'graph'|'infinite': if the object that gets passed in doesn't comply with this configuration
setting, an error will occur. Finite trees will error if an object/array reference is encountered more than once, determined by set membership of the WalkNode's val. Graphs will only process object/array references one time. Infinite trees will always process nodes -- use throw new Break() to end the processing manually. Warning: infinite trees will never complete processing if a callback doesn't throw new Break().

Config Defaults

const defaultConfig = {
    traversalMode: 'depth',
    rootObjectCallbacks: true,
    runCallbacks: true,
    graphMode: 'finiteTree',
    parallelizeAsyncCallbacks: false,
    callbacks: []
}

Using the builder

An alternative way to configure a walk is to use either the WalkBuilder or AsyncWalkBuilder.
Call WalkBuilder.walk(target: any) to execute the walk with the builder's configuration.
Example:
import { WalkBuilder } from 'walkjs';

const logCallback = (node: WalkNode) => console.log(node);
const myObject = {}

const result = new WalkBuilder()
    // runs for every node
    .withSimpleCallback(logCallback)
    // configured callback
    .withCallback({
        keyFilters: ['myKey'],
        positionFilter: 'postWalk',
        nodeTypeFilters: ['object'],
        executionOrder: 0,
        callback: logCallback
    })
    // alternative way to configure callbacks
    .withConfiguredCallback(logCallback)
        .filteredByKeys('key1', 'key2')
        .filteredByNodeTypes('object', 'array')
        .filteredByPosition('postWalk')
        .withFilter(node => !!node.parent)
        .withExecutionOrder(1)
        .done()
    .withGraphMode('graph')
    .withTraversalMode('breadth')
    .withRunningCallbacks(true)
    .withRootObjectCallbacks(true)
    // execute the walk
    .walk(myObject)

Callbacks

Callbacks are a way to execute custom functionality on certain nodes within our object tree. The general form of a callback object is:
{   
    executionOrder: 0,
    nodeTypeFilters: ['array'],
    keyFilters: ['friends'],
    positionFilter: 'preWalk',
    callback: function(node: NodeType){
        // do things here
    }
}

Here are the properties you can define in a callback configuration, most of which act as filters:
  • callback: (node: WalkNode) => void: the actual function to run. Your callback function will be passed a single argument: a WalkNode object (
see the Nodes section for more detail). succession. If unspecified, the callback will run 'preWalk'. For async functions, callback may alternatively return a Promise<void>, in which case it will be awaited.
  • executionOrder: number: an integer value for controlling order of callback operations. Lower values run earlier. If
unspecified, the order will default to 0. Callback stacks are grouped by position and property, so the sort will only apply to callbacks in the same grouping.
  • filters: ((node: WalkNode) => boolean)[]: A list of functions which will exclude nodes when the result of the function for that node is false.
  • nodeTypeFilters: NodeType[]: an array of node types to run on. Options are 'array', 'object', and 'value'. If unspecified,
the callback will run on any node type.
  • keyFilters: string[]: an array of key names to run on. The callback will check the key of the property against this list. If unspecified, the callback will run on any key.
  • positionFilter: PositionType: The position the traversal to run on -- think of this as when it should execute.
Options are 'preWalk' (before any list/object is traversed), and 'postWalk' (after any list/object is traversed). You may also supply 'both'. When the walk is run in 'breadth' mode, the only difference here is whether the callback is invoked prior to yielding the node. However when running in 'depth' mode, 'postWalk' callbacks for a node will run after all the callbacks of its children. For example, if our object is { a: b: { c: 1, d: 2 } }, we would expect 'postWalk' callbacks to run in the following order: c, d, b, a.

Nodes

WalkNode objects represent a single node in the tree, providing metadata about the value, its parents, siblings, and children. Nodes have the following properties:
  • key: string|number: The key of this property as defined on its parent. For example, if this callback is running on
the 'weight' property of a person, the key would be 'weight'. This will be the numerical index for members in arrays.
  • val: any: The value of the property. To use the above example, the value would be something like 183.
  • nodeType: NodeType: The type of node the property is. Possible NodeType are 'array' | 'object' | 'value'.
  • isRoot: boolean: A boolean that is set to true if the property is a root object, otherwise false.
  • executedCallbacks: Callback[]: An array of all callback functions that have already run on this property. The current function will not be in the list.
  • getPath(pathFormat?: (node: WalkNode) => string) The path to the value, formatted with the optional formatter passed in. For example, if the variable you're walking is named myObject, the path will
look something like ["friends"][10]["friends"][2]["name"], such that calling myObject["friends"][10]["friends"][2]["name"] will return the val. The pathFormat parameter should take a node and return the path segment for only that node; since getPath will automatically prepend the path of the node's parent as well.
  • parent: WalkNode: The node under which the property exists. node.parent is another instance of node, and will have all the same properties.
  • children: WalkNode[]: A list of all child nodes.
  • siblings: WalkNode[]: A list of all sibling nodes (nodes which share a parent).
  • descendants: WalkNode[]: A list of all descendant nodes (recursively traversing children).
  • ancestors: WalkNode[]: A list of nodes formed by recursively traversing parents back to the root.
Extra functions
Walk has some extra utility functions built-in that you may find useful.

Apply

apply(
    target: any, 
    ...callbacks: ((node: NodeType) => void)[]
): void
    
applyAsync(
    target: any, 
    ...callbacks: (((node: NodeType) => void) | ((node: NodeType) => Promise<void>))[]
): Promise<void>

A shorthand version of walk() that runs the supplied callbacks for all nodes.

Deep copy

deepCopy(target: object) : object

Returns a deep copy of an object, with all array and object references replaced with new objects/arrays.

Compare

compare(
    a: any, 
    b: any, 
    leavesOnly=false, 
    formatter: NodePathSegmentFormatter=defaultFormatter
): NodeComparison

This method does a deep comparison between objects a and b based on the keys of each node. It returns an array of the following type:
type NodeComparison = {
    path: string,
    a?: any
    b?: any
    hasDifference: boolean,
    difference?: 'added' | 'removed' | {before: any, after: any}
}

Reduce

reduce(
    source: object, 
    initialValue: T, 
    fn: (accumulator: T, node: WalkNode) => T
): T

This function accumulates a value of type T, starting with initialValue, by invoking fn on each node in source and adding the result to the accumulated value T.
Running walk as a generator
Behind the scenes, walk and walkAsync run as generators (Generator<WalkNode> and AsyncGenerator<WalkNode>, respectively). As they step through the object graph, nodes are yielded.
The default walk/walkAsync functions coerce the generator to a list before returning. However, you can access the generator directly; simply use the following imports instead:
import {walkStep, walkAsyncStep} from "walkjs";

// sync
for (const node of walkStep(obj, config))
    console.log(node);

// async
for await (const node of walkAsyncStep(obj, config))
    console.log(node)

preWalk callbacks are invoked prior to yielding a node, and postWalk callbacks after.