deepdash
Version:
➔ 𝐃eep standalone lib / 𝐋odash extension: ✓ eachDeep ✓ filterDeep ✓ mapDeep ✓ reduceDeep ✓ pickDeep ✓ omitDeep ✓ keysDeep ✓ index ✓ condenseDeep ⋮ Parents stack ⋮ Circular check ⋮ Leaves only mode ⋮ Children mode ⋮ cherry-pick ⋮ esm
652 lines (564 loc) • 18 kB
Markdown
# Deepdash
> v2.1.0 - [see changes](/changelog#v2-1-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',
tree:{
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`
- `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. Only elements of such collections will be passed into iteratee.
- `children` ('children') - children collection's field name, path, path's regex or array of any of this.
- `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.
```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 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)
- `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
```
```js
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.
```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.