import * as R from 'ramda'; interface Target { key: string; value?: any; } type BaseT = Record; export const treePaths = ( target: Target, childKey: string, data: Record, ): string[] => { const hasChildren = (node: T) => { return R.hasPath([childKey], node) && !R.isEmpty(node[childKey]); }; const Tree: any = { reduce: R.curry((reducerFn: any, init: any[], preNode: T, node: T) => { const acc = reducerFn(init, preNode, node); if (!hasChildren(node) && node[target.key] === target.value) { if(preNode.path){ acc.splice(0, 0, preNode.path); } return acc; } return node[childKey] && !R.isEmpty(node[childKey]) ? Tree.reduce(findFn, [], R.omit([childKey], node))(node[childKey]) : acc; }), find: R.curry((findFn: any, init: any[], preNode: T, node: T[]) => { return R.pipe( R.map(Tree.reduce(findFn, init, preNode)), R.flatten )(node as T[]); }) }; const reducerFn = (arr: any[], _preNode: T, data: T) => { if ( (target.value && data[target.key] && data[target.key] === target.value) || (!target.value && data[target.key]) ) { return arr.concat([data.path]); } else { return arr; } }; const findFn = (arr: any[], preNode: Record, data: Record) => { if(!data || R.isEmpty(data)){ return arr; } const keys = Object.keys(data); const result: any = []; keys.map((key: string) => { result.push(data[key]); }); return Tree.find(reducerFn, arr, preNode)(result); } const type = R.type(data); switch (type) { case 'Object': { return Tree.reduce(findFn, [], {})(data); } case 'Array': { return Tree.find(reducerFn, [], {})(data); } default: { return []; } } };