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
469 lines (429 loc) • 13.6 kB
Markdown
# Deepdash
> v1.9.5
Looking for eachDeep, filterDeep, omitDeep, 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
- [omitDeep](#omitdeep) - get object without keys specified as string name or regex
- [paths](#paths-keysdeep) - (keysDeep) get an array of paths
- [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, 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
c:object .b
d:object .b.c
0:object .b.c.d
i:number .b.c.d[0]
1:object .b.c.d
i:number .b.c.d[1]
2:object .b.c.d
i:number .b.c.d[2]
3:object .b.c.d
i:number .b.c.d[3]
4:object .b.c.d
i:number .b.c.d[4]
5:object .b.c.d
i:number .b.c.d[5]
6:object .b.c.d
o:object .b.c.d[6]
d:object .b.c.d[6].o
f:function .b.c.d[6].o
skip:object .b.c.d[6].o
s:string .b.c
b:boolean .b
n:number
u:undefined
nl:null
```
Chaining works too:
```js
_(obj).eachDeep((value, key, path, depth, parent, parentKey, parentPath) => {/* 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 to condense
);
```
**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
Make all the arrays in the object non-sparse.
```js
_.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:**
```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, // 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:**
```js
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.
```js
_.exists(
obj, // object to inspect
path, // path(string|array) to check for existense
)
```
**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, // 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:**
```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, 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.
```js
_.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:**
```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"]': {} }
```
## omitDeep
returns an object without keys specified as string name or regex
```js
_.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:**
```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(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.
```js
_.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:**
```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"]' ]
```
## pathToString
Converts given path from array to string format.
```js
_.pathToString(
path, // path in array 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.