JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 835
  • Score
    100M100P100Q107563F
  • License ISC

Walk or map over objects in a preorder or postorder manner.

Package Exports

  • obj-walker
  • obj-walker/dist/index.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 (obj-walker) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

obj-walker

Walk objects like this guy.

Walker, Texas Ranger

Map over an object in a preorder or postoder depth-first manner. Also, provides functions for serializing and deserializng self-referential objects using JSON pointer.

This library is designed to work well with functions that traverse an object in the same way JSON.stringify and JSON.parse do. Namely, preorder and postorder. To mimic that behavior entirely set the jsonCompat option to true.

Custom traversal functions are supported. This allows you to walk tree-like structures, such as JSON schema, in a more efficient and logical way. map does not work particularly well for this use case since the path to set values may be incompleted. Prefer walkie in these scenarios instead.

walker

walker(obj: object, walkFn: WalkFn, options: Options = {}) => void

Generic walking fn that traverse an object in preorder (default) or postorder, calling walkFn for each node. Can be used directly, but probably shouldn't.

export interface Node {
  key: string | undefined
  val: any
  parents: any[]
  path: string[]
  isLeaf: boolean
  isRoot: boolean
}

export interface Options {
  postOrder?: boolean
  jsonCompat?: boolean
  traverse?(x: any): any
}
import { walker } from 'obj-walker'

const obj = {
  a: {
    b: 23,
    c: 24,
  },
  d: {
    e: 'Bob',
    f: [10, 20, 30],
  },
}

const nodes: Node[] = []
const walkFn = (node: Node) => {
  nodes.push(node)
}
walker(obj, walkFn, options)
nodes

Returns an array of nodes. Note this is how walk works, so prefer that fn.

[
  {
    key: undefined,
    parents: [],
    val: { a: { b: 23, c: 24 }, d: { e: 'Bob', f: [10, 20, 30] } },
    path: [],
    isRoot: true,
    isLeaf: false,
  },
  {
    key: 'a',
    val: { b: 23, c: 24 },
    parents: [{ a: { b: 23, c: 24 }, d: { e: 'Bob', f: [10, 20, 30] } }],
    path: ['a'],
    isLeaf: false,
    isRoot: false,
  },
  {
    key: 'b',
    val: 23,
    parents: [
      { b: 23, c: 24 },
      { a: { b: 23, c: 24 }, d: { e: 'Bob', f: [10, 20, 30] } },
    ],
    path: ['a', 'b'],
    isLeaf: true,
    isRoot: false,
  },
  ...
]

walk

walk(obj: object, options: WalkOptions = {}) => Node[]

Walk an object. Returns an array of all nodes in the object in either preorder or postorder.

export interface WalkOptions extends Options {
  leavesOnly?: boolean
}
import { walk } from 'obj-walker'

const obj = {
  a: {
    b: 23,
    c: 24,
  },
  d: {
    e: 'Bob',
    f: [10, 20, 30],
  },
}
walk(obj).map((x) => x.path)

Produces:

;[
  [],
  ['a'],
  ['a', 'b'],
  ['a', 'c'],
  ['d'],
  ['d', 'e'],
  ['d', 'f'],
  ['d', 'f', '0'],
  ['d', 'f', '1'],
  ['d', 'f', '2'],
]

walkie

walkie(obj: object, walkFn: WalkFn, options: WalkOptions = {}) => object
export type WalkFn = (node: Node) => void

Walk-each ~ walkie

Walk over an object calling walkFn for each node. The original object is deep-cloned making it possible to simply mutate each node as needed in order to transform the object. The cloned object is returned.

Below I want to walk a MongoDB JSON schema and set additionalProperties to true wherever it exists. I traverse this tree using a custom traverse fn. The original object is not modified.

import { walkie } from 'obj-walker'

const obj = {
  bsonType: 'object',
  additionalProperties: false,
  required: ['name'],
  properties: {
    _id: {
      bsonType: 'objectId',
    },
    name: { bsonType: 'string' },
    addresses: {
      bsonType: 'array',
      items: {
        bsonType: 'object',
        additionalProperties: false,
        properties: {
          address: {
            bsonType: 'object',
            additionalProperties: false,
            properties: {
              zip: { bsonType: 'string' },
              country: { bsonType: 'string' },
            },
          },
        },
      },
    },
  },
}

const traverse = (x: any) => x.properties || (x.items && { items: x.items })
const walkFn = ({ val }: Node) => {
  if (val.hasOwnProperty('additionalProperties')) {
    val.additionalProperties = true
  }
}
walkie(obj, walkFn, { traverse })

Produces:

{
  bsonType: 'object',
  additionalProperties: true,
  required: ['name'],
  properties: {
    _id: { bsonType: 'objectId' },
    name: { bsonType: 'string' },
    addresses: {
      bsonType: 'array',
      items: {
        bsonType: 'object',
        additionalProperties: true,
        properties: {
          address: {
            bsonType: 'object',
            additionalProperties: true,
            properties: {
              zip: { bsonType: 'string' },
              country: { bsonType: 'string' },
            },
          },
        },
      },
    },
  },
}

map

Map over an object modifying values with a fn depth-first in a preorder or postorder manner. Exclude nodes by returning undefined. Undefined array values will not be excluded. The output of the mapper fn will be traversed if possible when traversing preorder.

map(obj: object, mapper: Mapper, options: Options = {}) => object
export type Mapper = (node: Node) => any
import { map } from 'obj-walker'

const obj = {
  a: {
    b: 23,
    c: 24,
  },
  d: {
    e: 'Bob',
    f: [10, null, 30, [31, undefined, 32], 40],
  },
  g: [25, '', { h: [null, 26, 27] }],
  i: 'Frank',
}
map(obj, ({ val }) => (Array.isArray(val) ? _.compact(val) : val))

Produces:

{
  a: { b: 23, c: 24 },
  d: { e: 'Bob', f: [10, 30, [31, 32], 40] },
  g: [25, { h: [26, 27] }],
  i: 'Frank',
}

Postorder

const obj = {
  bob: {
    scores: ['87', 'x97', 95, false],
  },
  joe: {
    scores: [92, 92.5, '73.2', ''],
  },
}
const result = map(
  obj,
  ({ val, isLeaf }) => {
    if (isLeaf) {
      return parseFloat(val)
    }
    return Array.isArray(val) ? _.compact(val) : val
  },
  { postOrder: true }
)

Produces:

{
  bob: { scores: [87, 95] },
  joe: { scores: [92, 92.5, 73.2] },
}

mapLeaves

mapLeaves(obj: object, mapper: Mapper, options?: Options) => object

Map over the leaves of an object, where a leaf is defined as a value that is not traversable according to either the default traverse fn which traverses plain objects and arrays, or your custom traverse fn.

Exclude nodes by returning undefined. Undefined array values will not be excluded.

import { mapLeaves } from 'obj-walker'

const obj = {
  a: {
    b: 23,
    c: 24,
  },
  d: {
    e: 100,
    f: [10, 20, 30],
  },
}
mapLeaves(obj, ({ val }) => val + 1)

Produces:

{
  a: { b: 24, c: 25 },
  d: { e: 101, f: [11, 21, 31] },
}

addRefs

addRefs(obj: object, options?: RefOptions): object

Replace duplicate objects refs with pointers to the first object seen.

import { addRefs } from 'obj-walker'

const apiOutput = {
  1: 'foo',
  2: 'bar',
  3: 'baz',
}

const detailsOutput = {
  1: 'bla',
  2: 'bla',
  3: 'bla',
}

const obj = {
  api: {
    input: [1, 2, 3],
    output: apiOutput,
  },
  details: {
    input: apiOutput,
    output: detailsOutput,
  },
  writeToDB: {
    input: detailsOutput,
  },
}
addRefs(obj)

Produces:

{
  api: {
    input: [1, 2, 3],
    output: { '1': 'foo', '2': 'bar', '3': 'baz' },
  },
  details: {
    input: { $ref: '#/api/output' },
    output: { '1': 'bla', '2': 'bla', '3': 'bla' },
  },
  writeToDB: { input: { $ref: '#/details/output' } },
}

deref

deref(obj: object, options?: RefOptions): object

Rehydrate objects by replacing refs with actual objects.

import { deref } from 'obj-walker'

const obj = {
  api: {
    input: [1, 2, 3],
    output: { '1': 'foo', '2': 'bar', '3': 'baz' },
  },
  details: {
    input: { $ref: '#/api/output' },
    output: { '1': 'bla', '2': 'bla', '3': 'bla' },
  },
  writeToDB: { input: { $ref: '#/details/output' } },
}
deref(obj)

Produces:

{
  api: {
    input: [1, 2, 3],
    output: { '1': 'foo', '2': 'bar', '3': 'baz' },
  },
  details: {
    input: { '1': 'foo', '2': 'bar', '3': 'baz' },
    output: { '1': 'bla', '2': 'bla', '3': 'bla' },
  },
  writeToDB: { input: { '1': 'bla', '2': 'bla', '3': 'bla' } },
}