UNPKG

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

806 lines (709 loc) 25.2 kB
# Deepdash > v3.1.0 - [see changes](/changelog#v3-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); ``` ## Demo [Example react+redux app](https://kw0zox7r.codesandbox.io/) with nested comments filtered by Deepdash.([play with code here](https://codesandbox.io/s/kw0zox7r)) # Usage <details> <summary><i>let obj = {/* ... */};</i></summary> ```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, }; ``` </details> ```js _.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 } }); ``` <details> <summary>Console:</summary> ``` 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 ``` </details> Chaining works too: ```js _(obj).eachDeep((value, key, parent, context) => {/* do */}).value(); ``` # 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: 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. ```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) - `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:** ```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 an object with childs of your choice only ```js _.filterDeep( obj, predicate, options={ checkCircular: false, keepCircular: true, // replaceCircularBy: <value>, condense: true, cloneDeep: _.cloneDeep, pathFormat: 'string', leavesOnly: !options.tree, 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](#iteratee) - 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` (!options.tree) - 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):** ```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; } ); 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)** ```js 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. ```js _.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:** ```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, 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:** ```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, 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:** ```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 }, bad2: { good: true }, good4: [ { good5: 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, 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:** ```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 }, bad2: { good: 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.