export interface HasChildren { children: Type[] } // Depth-first tree-traversal export const traverse = >( list: Type[], func: (a: Type) => void ) => { for (const obj of list) { func(obj) if (obj?.children) { if (!Array.isArray(obj.children)) { throw new Error(`obj.children is not an Array: ${JSON.stringify(obj)}`) } traverse(obj.children, func) } } } export const traverseLeafsFirst = >( list: Type[], func: (a: Type) => void ) => { for (const obj of list) { if (obj?.children) { if (!Array.isArray(obj.children)) { throw new Error(`obj.children is not an Array: ${JSON.stringify(obj)}`) } traverseLeafsFirst(obj.children, func) } func(obj) } } export const deepMap = < InputType extends HasChildren, OutputType extends HasChildren >( inputs: InputType[], func: (a: InputType) => OutputType ): OutputType[] => { return inputs.map(input => { const output = func(input) if (input?.children) { if (!Array.isArray(input.children)) { throw new Error( `obj.children is not an Array: ${JSON.stringify(input)}` ) } output.children = deepMap(input.children, func) } return output }) } export const deepFilter = >( objects: Type[], func: (a: Type) => boolean ): Type[] => { const result = [] for (const obj of objects) { if (!func(obj)) { // eslint-disable-next-line no-continue continue } let newObj = { ...obj } if (obj.children) { newObj = { ...obj, children: deepFilter(obj.children, func), } } result.push(newObj) } return result }