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

652 lines (564 loc) 18 kB
# 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.