Deepdash logoDeepdash logo

Deepdash

v3.1.0 - see changes

Looking for eachDeep, filterDeep, omitDeep, pickDeep, 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
  • paths - (keysDeep) get an array of paths
  • pickDeep - get object only with keys specified by names or regexes
  • omitDeep - get object without keys specified by names or regexes
  • 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);

Demo

Example react+redux app with nested comments filtered by Deepdash.(play with code here)

Usage

let obj = {/* ... */};
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, parent, context) => {
  console.log(
    _.repeat('  ', context.depth) +
      key +
      ':' +
      (value === null ? 'null' : typeof value),
    context.parent.path && ' @' + context.parent.path
  );
  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, parent, context) => {/* do */}).value();

Methods

condense

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

_.condense( arr ) => array
  • arr - array to condense
  • returns - 'condensed' array without holes.

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

Makes all the arrays in the object non-sparse.

_.condenseDeep( obj, options = { checkCircular: false } ) => object
  • obj - The object/array to iterate over.
  • options
    • checkCircular (false) - Check each value to not be one of the parents, to avoid circular references.
  • returns - 'condensed' object/array without holes.

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, iteratee=_.identity, options={
    checkCircular: false,
    pathFormat: 'string',
    tree: false,// true → { rootIsChildren: true, children: 'children' }
  }) => object
  • obj - The object/array to iterate over.
  • iteratee (_.identity) - The function invoked per iteration. Should return false explicitly to skip children of current node.
  • options
    • callbackAfterIterate (false) - invoke iteratee twice, before and after iteration over children. On second run context iteratee's argument will have afterIterate flag set to the true. By default, iteratee invoked before it's children only.
    • checkCircular (false) - Check each value to not be one of the parents, to avoid circular references.
    • pathFormat ('string') - specifies 'string' or 'array' format of paths passed to the iteratee.
    • tree (false) - treat the obj as a "tree" of nodes with children collections. Can be boolean or object.
      • children ('children') - children collection's field name, path, path's regex or array of any of this. Only elements of such collections will be passed into iteratee.
      • rootIsChildren (true) - treat obj as top-level children collection, so its elements will be passed into iteratee without parent path check (so you don't need to specify empty path as tree.children option)
  • returns - source object

iteratee

callback function which will be invoked for each child of object.

(value, key, parentValue, context) => boolean

iteratee arguments

  • value - current field or element
  • key|index - field name or array index of the value
  • parentValue - an object or an array which contains current value
  • context - an object with fields:
    • path - path to the current value
    • parent - an object of the current parent
      • value - value of the parent, equivalent of parentValue argument.
      • key - parent key|index
      • path - parent path
      • parent - grandparent with the same structure.
    • next parent fields are available if tree option was activated:
      • isTreeChildren - true if this parent is children collection.
      • treeChildrenPath - contains matched children path (specific one from tree.children option array)
      • isTreeNode - true if parent is an element of some children collection. Only this values will be passed into iteratee.
    • parents - an array with all parent objects starting from the root level. parent object listed above is just the last element of this array
    • obj - source object
    • depth - current value's nesting level
    • afterIterate - this flag will be true if it's a second invokation of the iteratee. See options.callbackAfterIterate for details.
  • next three fields are available if options.checkCircular was true, otherwise they will be undefined
    • isCircular - true if the current value is a circular reference.
    • circularParent - parent object from parents array referenced by current value or null if not isCircular.
    • circularParentIndex - index of circularParent in the parents array or -1
  • returns - return false explicitly to prevent iteration over current value's children

Example:

  let circular = { a: { b: { c: {} } } };
  circular.a.b.c = circular;
  _.eachDeep(circular, (value, key, parent, ctx) => {
    if (ctx.isCircular) {
      log.push(
        "Circular reference to "+ctx.circularParent.path+" skipped at " + ctx.path
      );
      return false;
    }
    //do your things
  },{ checkCircular: true });

Console:

  Circular reference to a skipped at a.b.c
  let children = [
    {
      name: 'grand 1',
      children: [
        {
          name: 'parent 1.1',
          children: [{ name: 'child 1.1.1' }, { name: 'child 1.1.2' }],
        },
        {
          name: 'parent 1.2',
          children: [{ name: 'child 1.2.1' }, { name: 'child 1.2.2' }],
        },
      ],
    },
    {
      name: 'grand 2',
      children: [
        {
          name: 'parent 2.1',
          children: [{ name: 'child 2.1.1' }, { name: 'child 2.1.2' }],
        },
        {
          name: 'parent 2.2',
          children: [{ name: 'child 2.2.1' }, { name: 'child 2.2.2' }],
        },
      ],
    },
  ];
  let total = 0;
  _.eachDeep(
    children,
    (child, i, parent, ctx) => {
      console.log(_.repeat('  ', ctx.depth) + child.name);
      total++;
    },
    { tree: true }
  );
  console.log('total nodes: ' + total);

Console:

  grand 1
      parent 1.1
          child 1.1.1
          child 1.1.2
      parent 1.2
          child 1.2.1
          child 1.2.2
  grand 2
      parent 2.1
          child 2.1.1
          child 2.1.2
      parent 2.2
          child 2.2.1
          child 2.2.2
  total nodes: 14

exists

Check if path exists in the object considering sparse arrays. Unlike Lodash's has - exists returns false for empty array slots.

_.exists( obj, path ) => boolean
  • obj - object to inspect
  • path - path(string|array) to check for existense
  • returns - true if path exists, otherwise false.

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 an object with childs of your choice only

_.filterDeep( obj, predicate, options={
    checkCircular: false,
    keepCircular: true,
    // replaceCircularBy: <value>,
    condense: true,
    cloneDeep: _.cloneDeep,
    pathFormat: 'string',
    leavesOnly: true,
    tree: false, // true → { rootIsChildren: true, children: 'children' }
    onTrue: {
      skipChildren: true,   // false if options.tree
      cloneDeep: true,
      keepIfEmpty: true },
    onUndefined: {
      skipChildren: false,  // false if options.tree
      cloneDeep: false,     // true if options.tree
      keepIfEmpty: false },
    onFalse: {
      skipChildren: true,   // false if options.tree
      cloneDeep: false,     // true if options.tree
      keepIfEmpty: false },
  }) => object
  • obj - The object/array to iterate over.
  • predicate - The predicate is invoked with same arguments as described in iteratee subsection
    • If returns true - current value will be deeply cloned to the result object, iteration over children of this value will skipped if no tree option set. This behavior can be adjusted in options.onTrue
    • If returns undefined - current path will only appear in the result object if some child elements will pass the filter during subsequent iterations. In the 'tree' mode, all the node will be cloned, but again, only if some children passed the filter. This default behavior can be changed in options.onUndefined
    • If returns false - current value will be completely excluded from the result object, iteration over children of this value will be skipped. In the 'tree' mode default action of the false case is the same as for undefined. See options.onFalse option.
    • You can also return an object with skipChildren, cloneDeep and keepIfEmpty boolean fields to control the filtering process directly.
  • 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 (not defaults) - Specify the value to replace circular references by.
    • condense (true) - Condense the result object (excluding some paths may produce sparse arrays)
    • cloneDeep (_.cloneDeep)- Method to use for deep cloning values, Lodash cloneDeep by default.
    • pathFormat ('string') - specifies 'string' or 'array' format of paths passed to the iteratee.
    • leavesOnly (true) - Call predicate for childless values only. Incompatible with options.tree.
    • tree (false) - treat the obj as a "tree" of nodes with children collections. Can be boolean or object.
      • children ('children') - children collection's field name, path, path's regex or array of any of this. Only elements of such collections will be passed into iteratee.
      • rootIsChildren (true) - treat obj as top-level children collection, so its elements will be passed into iteratee without parent path check (so you don't need to specify empty path as tree.children option)
    • onTrue (object) - Describes how current value should be prcoessed if iteratee returns true
      • skipChildren (!options.tree) - skip or iterate over value's children
      • cloneDeep (true) - deeply clone current value into result or copy primitives only and create empty array/object without nested data.
      • keepIfEmpty (true) - keep empty array/object in the result, if all the children were filtered out/not exist.
    • onUndefined (object) - Describes how current value should be prcoessed if iteratee returns undefined
      • skipChildren (false)
      • cloneDeep (!!options.tree)
      • keepIfEmpty (false)
    • onFalse (object) - Describes how current value should be prcoessed if iteratee returns true
      • skipChildren (!options.tree)
      • cloneDeep (!!options.tree)
      • keepIfEmpty (false)
  • returns - deeply filtered object/array

Example(fields iteration):

  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, parent) => {
      if (key == 'name' && parent.good) return true;
      if (key == 'good' && value == true) return 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 } } ] }

Example(tree iteration)

let badChildren = [
  {
    name: '1',
    bad: false,
    children: [
      { name: '1.1', bad: false },
      { name: '1.2' },
      { name: '1.3', bad: true },
    ],
  },
  {
    name: '2',
    children: [
      { name: '2.1', bad: false },
      { name: '2.2' },
      { name: '2.3', bad: true },
    ],
  },
  {
    name: '3',
    bad: true,
    children: [
      { name: '3.1', bad: false },
      { name: '3.2' },
      { name: '3.3', bad: true },
    ],
  },
  ];

let reallyBad = _.filterDeep(badChildren, 'bad', { tree: true });
console.log(reallyBad);

Console:

[
  {
    "name": "1",
    "bad": false,
    "children": [
      {
        "name": "1.3",
        "bad": true
      }
    ]
  },
  {
    "name": "2",
    "children": [
      {
        "name": "2.3",
        "bad": true
      }
    ]
  },
  {
    "name": "3",
    "bad": true,
    "children": [
      {
        "name": "3.3",
        "bad": true
      }
    ]
  }
]

indexate

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

_.indexate( obj, options={
    checkCircular: false,
    includeCircularPath: true,
    leavesOnly: true,
    tree: false // true → { rootIsChildren: true, children: 'children' }
  }) => object
  • 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.
    • tree (false) - treat the obj as a "tree" of nodes with children collections. Can be boolean or object.
      • children ('children') - children collection's field name, path, path's regex or array of any of this. Only elements of such collections will be listed.
      • rootIsChildren (true) - treat obj as top-level children collection, so its elements will listed in the result without parent path check (so you don't need to specify empty path as tree.children option)
  • returns - 'index' object

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"]': {} }

paths (keysDeep)

Creates an array of the paths of object or array.

_.paths( obj, options={
    checkCircular: false,
    includeCircularPath: true,
    pathFormat: 'string',
    leavesOnly: true,
    tree: false // true → { rootIsChildren: true, children: 'children' }
  }) => array
  • 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
    • pathFormat ('string') - specifies 'string' or 'array' format of paths passed to the iteratee.
    • leavesOnly (true) - Return paths to childless values only.
    • tree (false) - treat the obj as a "tree" of nodes with children collections. Can be boolean or object.
      • children ('children') - children collection's field name, path, path's regex or array of any of this. Only elements of such collections will be listed.
      • rootIsChildren (true) - treat obj as top-level children collection, so its elements will listed in the result without parent path check (so you don't need to specify empty path as tree.children option)
  • returns - array with paths of the object, formatted as strings or as array

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"]' ]

pickDeep

returns an object only with keys specified by names or regexes

_.pickDeep( obj, keys, options={
    checkCircular: false,
    keepCircular: true,
    // replaceCircularBy: <value>,
    condense: true,
    onMatch: {
      cloneDeep: false,
      skipChildren: false,
      keepIfEmpty: true,
    },
    onNotMatch: {
      cloneDeep: false,
      skipChildren: false,
      keepIfEmpty: false,
    }
  }) => object
  • obj - The object/array to pick from.
  • paths - path or array of paths to pick. Can be string or 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 (no defaults) - Specify the value to replace circular references by.
    • condense (true) - Condense the result object, since excluding some paths may produce sparse arrays.
    • onMatch (object) - describes how current value should be processed, if current path matches the criteria. By default it will be copied into result object without deep cloning, and all it's deeper children will be inspected.
      • skipChildren (false) - skip or iterate over value's children
      • cloneDeep (false) - deeply clone current value into result or copy primitives only and create empty array/object without nested data.
      • keepIfEmpty (true) - keep empty array/object in the result, if all the children were filtered out/not exist.
    • onNotMatch (object) - describes how current value should be processed, if current path NOT matches the criteria. By default it will be completely excluded from the result object and deeper children check will be skiped.
      • cloneDeep (false)
      • skipChildren (false)
      • keepIfEmpty (false)
  • returns - object/array with picked values only

Example:

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

Console:

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

omitDeep

returns an object without keys specified by names or regexes

_.omitDeep( obj, keys, options={
    checkCircular: false,
    keepCircular: true,
    // replaceCircularBy: <value>,
    condense: true,
    onMatch: {
      cloneDeep: false,
      skipChildren: false,
      keepIfEmpty: false,
    },
    onNotMatch: {
      cloneDeep: false,
      skipChildren: false,
      keepIfEmpty: true,
    }
  }) => object
  • obj - The object to omit from.
  • keys - key or array of keys to exclude. Can be string or 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 (no defaults) - Specify the value to replace circular references by.
    • condense (true) - Condense the result object, since excluding some paths may produce sparse arrays
    • onMatch (object) - describes how current value should be processed, if current path matches the criteria. By default it will be completely excluded from the result object and deeper children check will be skiped.
      • skipChildren (false) - skip or iterate over value's children
      • cloneDeep (false) - deeply clone current value into result or copy primitives only and create empty array/object without nested data.
      • keepIfEmpty (false) - keep empty array/object in the result, if all the children were filtered out/not exist.
    • onNotMatch (object) - describes how current value should be processed, if current path NOT matches the criteria. By default it will be copied into result object without deep cloning, and all it's deeper children will be inspected.
      • cloneDeep (false)
      • skipChildren (false)
      • keepIfEmpty (true)
  • returns - object without specified values.

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(clean);
  clean = _.omitDeep(obj, /^bad.*$/);
  console.log(clean);

Console:

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

pathToString

Converts given path from array to string format.

_.pathToString( path ) => string;
  • path - path in array format
  • returns - path in string 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.


Docs

  • Contact