# Deepdash

> v2.0.0 - [see changes](/changelog#v2-0-0)

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

> Deepdash lib is used in [PlanZed.org](https://planzed.org/) - awesome cloud mind map app created by the author of deepdash.
Plz check it, it's free and I need [feedback](https://github.com/YuriGor/PlanZed.org) 😉

## List of Methods

- [condense](#condense) - condense sparse array
- [condenseDeep](#condensedeep) - condense all the nested arrays
- [eachDeep](#eachdeep-foreachdeep) - (forEachDeep) iterate over all the children and sub-children
- [exists](#exists) - like a `_.has` but returns `false` for empty array slots
- [filterDeep](#filterdeep) - deep filter object
- [indexate](#indexate) - get an object with all the paths as keys and corresponding values
- [paths](#paths-keysdeep) - (keysDeep) get an array of paths
- [pickDeep](#pickdeep) - get object only with keys specified by names or regexes
- [omitDeep](#omitdeep) - get object without keys specified by names or regexes
- [pathToString](#pathtostring) - convert an array to string path (opposite to _.toPath)

### Installation
In a browser load [script](https://cdn.jsdelivr.net/npm/deepdash/deepdash.min.js) after Lodash:

```html
<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):

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

Or as [ECMAScript Module](https://nodejs.org/api/esm.html#esm_ecmascript_modules):

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

# Usage

```js
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:

```js
  _(obj).eachDeep((value, key, parent, context) => {/* do */}).value();
```

# Tutorials
[filterDeep,indexate and condenseDeep](http://yurigor.com/deep-filter-js-object-or-array-with-lodash/)

# Methods

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

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

**Example:**

```js
  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.

```js
_.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:**

```js
  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.

```js
_.eachDeep( obj, iteratee=_.identity, options={ checkCircular: false, pathFormat: 'string' }) => 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`
    - `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.
* `returns` - source object

### iteratee
callback function which will be invoked for each child of object.
```js
(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 with the current parent's `value`, `key|index`, `path` and `parent` fields
    - `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

* 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:**
```js
  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
```
## exists

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

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

**Example:**
```js
  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
```js
_.filterDeep( obj, predicate, options={
    checkCircular: false,
    keepCircular: true,
    // replaceCircularBy: <value>,
    leavesOnly: true,
    condense: true,
    cloneDeep: _.cloneDeep,
    pathFormat: 'string',
    keepUndefined: false
  }) => object
```
* `obj` - The object/array to iterate over.
* `predicate` - The predicate is invoked with same arguments as described in [iteratee subsection](#iteratee)
    - If returns `true` - current value will be deeply cloned to the result object, iteration over children of this value will skipped.
    - If returns `false` - current value will be completely excluded from the result object, iteration over children of this value will skipped.
    - If returns `undefined` - current path will only appear in the result object if some child elements will pass the filter during subsequent iterations or if `options.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` (not defaults) - Specify the value to replace circular references by.
    - `leavesOnly` (true) - Call predicate for childless values only.
    - `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.
    - `keepUndefined` (false) - keep field in the result object if iteratee returned undefined. Non-leaves will be empty.
* `returns` - deeply filtered object/array

**Example:**
```js
  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;
    },
    { 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.

```js
_.indexate( obj, options={
    checkCircular: false,
    includeCircularPath: true,
    leavesOnly: true
  }) => 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.
* `returns` - 'index' object

**Example:**

```js
  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.

```js
_.paths( obj, options={
    checkCircular: false,
    includeCircularPath: true,
    leavesOnly: true,
    pathFormat: 'string'
  }) => 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`
    - `leavesOnly` (true) - Return paths to childless values only.
    - `pathFormat` ('string') - specifies `'string'` or `'array'` format of paths passed to the iteratee.
* `returns` - array with paths of the object, formatted as strings or as array

**Example:**

```js
  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

```js
_.pickDeep( obj, keys, options={
    checkCircular: false,
    keepCircular: true,
    // replaceCircularBy: <value>,
    condense: true
  }) => object
```
* `obj` - The object/array to pick from.
* `keys` - key or array of keys 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.
* `returns` - object/array with picked values only

**Example:**

```js
  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, bad3: true },
  bad2: { good: true },
  good4: [ { good5: true, bad5: true } ] }
```

## omitDeep

returns an object without keys specified by names or regexes

```js
_.omitDeep( obj, keys, options={
    checkCircular: false,
    keepCircular: true,
    // replaceCircularBy: <value>,
    condense: 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
* `returns` - object without specified values.

**Example:**

```js
  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 },
  good4: [ { good5: true } ] }
```

## pathToString

Converts given path from array to string format.

```js
_.pathToString( path ) => string;
```
* `path` - path in array format
* `returns` - path in string format

**Example:**

```js
  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](https://github.com/YuriGor/deepdash/issues/new) other methods implementation.
