Deepdash logoDeepdash logo

Deepdash

Looking for eachDeep, filterDeep, omitDeep, keysDeep etc? Tree traversal extension for Lodash.

List of Methods

  • condense - condense sparse array
  • condenseDeep - condense all the nested arrays
  • eachDeep - (forEachDeep) iterate over all the children and sub-children
  • exists - like a _.has but returns false for empty array slots
  • filterDeep - deep filter object
  • indexate - get an object with all the paths as keys and corresponding values
  • omitDeep - get object without keys specified as string name or regex
  • paths - (keysDeep) get an array of paths
  • pathToString - convert an array to string path (opposite to _.toPath)

Installation

In a browser load script after Lodash:

<script src="https://cdn.jsdelivr.net/npm/lodash/lodash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/deepdash/deepdash.min.js"></script>

Using npm:

npm i --save deepdash

In Node.js (same for the Angular component):

//mixin new methods into Lodash object
const _ = require('deepdash')(require('lodash'));

Or as ECMAScript Module:

import lodash from "lodash";
import deepdash from "deepdash";
const _ = deepdash(lodash);

Usage

let obj = {
  a: {
    b: {
      c: {
        d: [
          { i: 0 },
          { i: 1 },
          { i: 2 },
          { i: 3 },
          { i: 4 },
          { i: 5 },
          {
            o: {
              d: new Date(),
              f: function() {},
              skip: {
                please: {
                  dont: {
                    go: {
                      here: 'skip it',
                    },
                  },
                },
              },
            },
          },
        ],
        s: 'hello',
      },
      b: true,
    },
    n: 12345,
    u: undefined,
  },
  nl: null,
};
_.eachDeep(obj, (value, key, path, depth, parent, parentKey, parentPath) => {
  console.log(
    _.repeat('  ', depth) +
      key +
      ':' +
      (value === null ? 'null' : typeof value),
    parentPath && ' @' + parentPath
  );
  if(key=="skip"){
    return false; // return false explicitly to skip iteration over current value's children
  }
});

Console:

a:object
  b:object  @a
    c:object  @a.b
      d:object  @a.b.c
        0:object  @a.b.c.d
          i:number  @a.b.c.d[0]
        1:object  @a.b.c.d
          i:number  @a.b.c.d[1]
        2:object  @a.b.c.d
          i:number  @a.b.c.d[2]
        3:object  @a.b.c.d
          i:number  @a.b.c.d[3]
        4:object  @a.b.c.d
          i:number  @a.b.c.d[4]
        5:object  @a.b.c.d
          i:number  @a.b.c.d[5]
        6:object  @a.b.c.d
          o:object  @a.b.c.d[6]
            d:object  @a.b.c.d[6].o
            f:function  @a.b.c.d[6].o
            skip:object  @a.b.c.d[6].o
      s:string  @a.b.c
    b:boolean  @a.b
  n:number  @a
  u:undefined  @a
nl:null

Chaining works too:

  _(obj).eachDeep((value, key, path, depth, parent, parentKey, parentPath) => {/* do */}).value();

Tutorials

filterDeep,indexate and condenseDeep

Methods

condense

Makes sparse array non-sparse. This method mutates object.

_.condense(
  arr // array to condense
);

Example:

  let arr = ['a', 'b', 'c', 'd', 'e'];
  delete arr[1];
  console.log(arr);
  delete arr[3];
  console.log(arr);
  _.condense(arr);
  console.log(arr);

Console:

  [ 'a', <1 empty item>, 'c', 'd', 'e' ]
  [ 'a', <1 empty item>, 'c', <1 empty item>, 'e' ]
  [ 'a', 'c', 'e' ]

condenseDeep

Make all the arrays in the object non-sparse.

_.condenseDeep(
  obj,                  // The object to iterate over.
  options = {
    checkCircular: false, // Check each value to not be one of the parents, to avoid circular references.
  }
);

Example:

  let obj = { arr: ['a', 'b', { c: [1, , 2, , 3] }, 'd', 'e'] };
  delete obj.arr[1];
  delete obj.arr[3];
  _.condenseDeep(obj);
  console.log(obj);

Console:

  { arr: [ 'a', { c: [ 1, 2, 3 ] }, 'e' ] }

eachDeep (forEachDeep)

Invokes given callback for each field and element of given object or array, nested too.

_.eachDeep(
  obj,                  // The object to iterate over
  iteratee=_.identity,  // The function invoked per iteration. Return `false` explicitly to skip children of current node.
  options={
    track: false,       /* track parents from current back to the root,
                          useful for circular reference detecting.
                          If true, `iteratee` will have additional `parents` object argument
                          with `values`, `keys` and `paths` arrays inside. */
    pathFormat: 'string'/* 'string'|'array' - specifies the format of paths passed to the iteratee.
                          'array' is better for performance. 'string' is better for readability. */
  }
)

Example:

  let circular = { a: { b: { c: {} } } };
  circular.a.b.c = circular;
  _.eachDeep(
    circular,
    (value, key, path, depth, parent, parentKey, parentPath, parents) => {
      if (_.indexOf(parents.values, value) !== -1) {
        console.log(
          "Circular reference skipped for '" + key + "' at " + parentPath
        );
        return false; // if `false` returned explicitly, children of current `value` will be skipped.
      }
      //do your things
    }
  ,{track:true});

Console:

  Circular reference skipped for 'c' at a.b

exists

Check if path exists in the object considering sparse arrays. Alternative for Lodash has method, which returns true for empty array slots.

_.exists(
  obj,  // object to inspect
  path, // path(string|array) to check for existense
)

Example:

  var obj = [,{a:[,'b']}];
  _.exists(obj, 0); // false
  _.exists(obj, 1); // true
  _.exists(obj, '[1].a[0]'); // false
  _.exists(obj, '[1].a[1]'); // true

filterDeep

Returns and object with childs of your choice only

_.filterDeep(
  obj,                             // The object to iterate over.
  predicate,                       /* The predicate is invoked with eight arguments:
                                      (value, key|index, path, depth, parent, parentKey, parentPath, parents)
                                      - If predicate returns `true` - value will be deeply cloned to the result object,
                                      no further iteration over children of this value will be performed.
                                      - If predicate returns `false` - value will be completely excluded from the result object,
                                      no further iteration over children of this value will be performed.
                                      - If predicate returns `undefined` - current path will only appear in the result object
                                      if some child elements will pass the filter during subsequent iterations or if keepUndefined=true. */
  options = {
    checkCircular: false,          // Check each value to not be one of the parents, to avoid circular references.
    keepCircular: true,            // The result object will contain circular references if they passed the filter.
    // replaceCircularBy: <value>, // Specify the value to replace circular references by.
    leavesOnly: true,               /* Call predicate for childless values only by default.
                                      Or, if set to false, all the intermediate objects will be passed into the predicate, including parents. */
    condense: true,                // Condense the result object, since excluding some paths may produce sparse arrays
    cloneDeep: _.cloneDeep,        // Method to use for deep cloning values, Lodash cloneDeep by default.
    pathFormat: 'string',          /* 'string'|'array' - specifies the format of paths passed to the iteratee.
                                      'array' is better for performance. 'string' is better for readability. */
    keepUndefined: false,          /* keep field in the result object if iteratee returned undefined */
  }
)

Example:

  let things = {
    things: [
      { name: 'something', good: false },
      {
        name: 'another thing', good: true,
        children: [
          { name: 'child thing 1', good: false },
          { name: 'child thing 2', good: true },
          { name: 'child thing 3', good: false },
        ],
      },
      {
        name: 'something else', good: true,
        subItem: { name: 'sub-item', good: false },
        subItem2: { name: 'sub-item-2', good: true },
      },
    ],
  };
  let filtrate = _.filterDeep(
    things,
    (value, key, path, depth, parent, parentKey, parentPath, parents) => {
      if (key == 'name' && parent.good) return true;
      if (key == 'good' && value == true) return true;
    },
    { leavesOnly: true }
  );
  console.log(filtrate);

Console:

  { things:
   [ { name: 'another thing',
       good: true,
       children: [ { name: 'child thing 2', good: true } ] },
     { name: 'something else',
       good: true,
       subItem2: { name: 'sub-item-2', good: true } } ] }

indexate

Creates an 'index' flat object with paths as keys and corresponding values.

_.indexate(
  obj,                          // The object to iterate over.
  options={
    checkCircular: false,       // Check each value to not be one of the parents, to avoid circular references.
    includeCircularPath: true,  /* If found some circular reference - include a path to it into the result or skip it.
                                   Option ignored if `checkCircular:false`. */
    leavesOnly: true             /* Return paths to childless values only by default.
                                   Or all the paths will be returned, including parents, if set to false. */
  }
)

Example:

  let index = _.indexate(
    {
      a: {
        b: {
          c: [1, 2, 3],
          'hello world': {},
        },
      },
    },
    { leavesOnly: true }
  );
  console.log(index);

Console:

  { 'a.b.c[0]': 1,
    'a.b.c[1]': 2,
    'a.b.c[2]': 3,
    'a.b["hello world"]': {} }

omitDeep

returns an object without keys specified as string name or regex

_.omitDeep(
  obj,                             // The object to iterate over.
  key,                             // key or array of keys to exclude. Can be regex.
  options = {
    checkCircular: false,          // Check each value to not be one of the parents, to avoid circular references.
    keepCircular: true,            // The result object will contain circular references if they passed the filter.
    // replaceCircularBy: <value>, // Specify the value to replace circular references by.
    condense: true,                // Condense the result object, since excluding some paths may produce sparse arrays
  }
)

Example:

  let obj = {
    good1: true,
    bad1: false,
    good2: { good3: true, bad3: false },
    bad2: { good: true },
    good4: [{ good5: true, bad5: false }],
    bad4: [],
  };
  var clean = _.omitDeep(obj, ['bad1', 'bad2', 'bad3', 'bad4', 'bad5']);
  console.log(paths);
  clean = _.omitDeep(obj, /^bad.*$/);
  console.log(paths);

Console:

{ good1: true,
  good2: { good3: true },
  good4: [ { good5: true } ] }

paths (keysDeep)

Creates an array of the paths of object or array.

_.paths(
  obj,                         // The object to iterate over.
  options = {
    checkCircular: false,      // Check each value to not be one of the parents, to avoid circular references.
    includeCircularPath: true, /* If found some circular reference - include a path to it into the result or skip it.
                                  Option ignored if `checkCircular:false`. */
    leavesOnly: true,           /* Return paths to childless values only by default.
                                  Or all the paths will be returned, including parents, if set to false. */
    pathFormat: 'string',      /* 'string'|'array' - specifies the format of paths.
                                  'array' is better for performance. 'string' is better for readability. */
  }
)

Example:

  let paths = _.paths({
    a: {
      b: {
        c: [1, 2, 3],
        "hello world":{}
      },
    },
  },{ leavesOnly: false });
  console.log(paths);
  paths = _.paths({
    a: {
      b: {
        c: [1, 2, 3],
        "hello world":{}
      },
    },
  });
  console.log(paths);

Console:

  [ 'a',
    'a.b',
    'a.b.c',
    'a.b.c[0]',
    'a.b.c[1]',
    'a.b.c[2]',
    'a.b["hello world"]' ]

  [
    'a.b.c[0]',
    'a.b.c[1]',
    'a.b.c[2]',
    'a.b["hello world"]' ]

pathToString

Converts given path from array to string format.

_.pathToString(
  path, // path in array format
);

Example:

  console.log(_.pathToString(['a', 'b', 'c', 'defg', 0, '1', 2.3]));

Console:

  a.b.c.defg[0][1]["2.3"]

Other traversal methods

Feel free to request other methods implementation.

v1.9.5