Skip to content

Instantly share code, notes, and snippets.

@JamieMason
Last active June 18, 2021 06:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JamieMason/196f1b87208238812fde9a47e93ea5e9 to your computer and use it in GitHub Desktop.
Save JamieMason/196f1b87208238812fde9a47e93ea5e9 to your computer and use it in GitHub Desktop.
JavaScript Generator function to deeply traverse JSON-Encodable data

JavaScript Generator function to deeply traverse JSON-Encodable data

Helpers

const isArray = value => Array.isArray(value);
const isObject = value => Object.prototype.toString.call(value) === '[object Object]';

walkTree

function* walkTree(collection) {
  function* visit(value, path, parentNode) {
    const node = Object.create({ parentNode });
    node.path = path;
    node.value = value;
    yield node;
    if (isArray(value)) {
      for (let i = 0, len = value.length; i < len; i++) {
        yield* visit(value[i], path.concat(i), node);
      }
    } else if (isObject(value)) {
      for (const key in value) {
        if (value.hasOwnProperty(key)) {
          yield* visit(value[key], path.concat(key), node);
        }
      }
    }
  }
  yield* visit(collection, [], null);
}

Example

const treeWalker = walkTree({ a: { b: { c: [1, 2] } } });

for (const node of treeWalker) {
  console.log(node, node.__proto__);
}

Output:

{ path: [], value: { a: { b: [Object] } } } { parentNode: null }
{ path: [ 'a' ], value: { b: { c: [Array] } } } { parentNode: { path: [], value: { a: [Object] } } }
{ path: [ 'a', 'b' ], value: { c: [ 1, 2 ] } } { parentNode: { path: [ 'a' ], value: { b: [Object] } } }
{ path: [ 'a', 'b', 'c' ], value: [ 1, 2 ] } { parentNode: { path: [ 'a', 'b' ], value: { c: [Array] } } }
{ path: [ 'a', 'b', 'c', 0 ], value: 1 } { parentNode: { path: [ 'a', 'b', 'c' ], value: [ 1, 2 ] } }
{ path: [ 'a', 'b', 'c', 1 ], value: 2 } { parentNode: { path: [ 'a', 'b', 'c' ], value: [ 1, 2 ] } }

walkTreeWithTypes

function* walkTreeWithTypes(collection) {
  const treeWalker = walkTree(collection);
  let next;
  while ((next = treeWalker.next()) && !next.done) {
    const superNode = next.value;
    const parentNode = superNode.parentNode;
    let node;
    if (isArray(superNode.value)) {
      node = Object.create({ isArray: true, isBranch: true, parentNode });
    } else if (isObject(superNode.value)) {
      node = Object.create({ isBranch: true, isObject: true, parentNode });
    } else {
      node = Object.create({ isLeaf: true, parentNode });
    }
    node.path = superNode.path;
    node.value = superNode.value;
    yield node;
  }
}

Example

const typesTreeWalker = walkTreeWithTypes({ a: { b: { c: [1, 2] } } });

for (const node of typesTreeWalker) {
  console.log(node, node.__proto__);
}

Output:

{ path: [], value: { a: { b: [Object] } } } { isBranch: true, isObject: true, parentNode: null }
{ path: [ 'a' ], value: { b: { c: [Array] } } } { isBranch: true,
  isObject: true,
  parentNode: { path: [], value: { a: [Object] } } }
{ path: [ 'a', 'b' ], value: { c: [ 1, 2 ] } } { isBranch: true,
  isObject: true,
  parentNode: { path: [ 'a' ], value: { b: [Object] } } }
{ path: [ 'a', 'b', 'c' ], value: [ 1, 2 ] } { isArray: true,
  isBranch: true,
  parentNode: { path: [ 'a', 'b' ], value: { c: [Array] } } }
{ path: [ 'a', 'b', 'c', 0 ], value: 1 } { isLeaf: true,
  parentNode: { path: [ 'a', 'b', 'c' ], value: [ 1, 2 ] } }
{ path: [ 'a', 'b', 'c', 1 ], value: 2 } { isLeaf: true,
  parentNode: { path: [ 'a', 'b', 'c' ], value: [ 1, 2 ] } }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment