We can write makeTree as a generic function with 3 parameters -
- input as a flat list of nodes
- a function that identifies each node's parent
- a function that builds the tree nodes in your desired shape
const source =
[{node:1,text:"pen",parent:null },{node:11,text:"pencil",parent:1},{node:12,text:"mango",parent:1},{node:111,text:"mango",parent:11},{node:112,text:"banana",parent:11},{node:211,text:"Cilli",parent:12},{node:1111,text:"banana",parent:111},{node:2,text:"Grapes",parent:null},{node:21,text:"Mango",parent:2},{node:3,text:"banana",parent:null},]
const result =
makeTree
( source // 1
, (node) => node.parent // 2
, (node, next) => ({ ...node, children: next(node.node) }) // 3
)
makeTree is suitably generic allowing it to work on any array of inputs. And we have a terrific opportunity to learn about mutual recursion -
many takes many inputs and calls one for each
one builds a new node and calls many for all children
This circular arrangement turns out to be a perfect fit for branching recursive structures like the tree in your desired output -
function makeTree(input, indexer, maker, root = null) {
const index = makeIndex(input, indexer)
const many = (all = []) => all.map(one) // 1
const one = (single = {}) => maker(single, r => many(index.get(r))) // 2
return many(index.get(root))
}
makeTree is super fast even for large inputs thanks to its use of another generic, makeIndex, which uses a Map for fast lookups -
function makeIndex(items, indexer) {
const insert = (r, k, v) => r.set(k, (r.get(k) ?? []).concat([ v ]))
return items.reduce((r, i) => insert(r, indexer(i), i), new Map)
}
We're done with the implementation. Let's see the result -
console.log(JSON.stringify(result, null, 2))
Expand the snippet below to verify the result in your own browser -
function makeTree(input, indexer, maker, root = null) {
const index = makeIndex(input, indexer)
const many = (all = []) => all.map(one)
const one = (single = {}) => maker(single, r => many(index.get(r)))
return many(index.get(root))
}
function makeIndex(items, indexer) {
const insert = (r, k, v) => r.set(k, (r.get(k) ?? []).concat([ v ]))
return items.reduce((r, i) => insert(r, indexer(i), i), new Map)
}
const source =
[{node:1,text:"pen",parent:null },{node:11,text:"pencil",parent:1},{node:12,text:"mango",parent:1},{node:111,text:"mango",parent:11},{node:112,text:"banana",parent:11},{node:211,text:"Cilli",parent:12},{node:1111,text:"banana",parent:111},{node:2,text:"Grapes",parent:null},{node:21,text:"Mango",parent:2},{node:3,text:"banana",parent:null},]
const result =
makeTree
( source
, (node) => node.parent
, (node, next) => ({ ...node, children: next(node.node) })
)
console.log(JSON.stringify(result, null, 2))
[
{
"node": 1,
"text": "pen",
"parent": null,
"children": [
{
"node": 11,
"text": "pencil",
"parent": 1,
"children": [
{
"node": 111,
"text": "mango",
"parent": 11,
"children": [
{
"node": 1111,
"text": "banana",
"parent": 111,
"children": []
}
]
},
{
"node": 112,
"text": "banana",
"parent": 11,
"children": []
}
]
},
{
"node": 12,
"text": "mango",
"parent": 1,
"children": [
{
"node": 211,
"text": "Cilli",
"parent": 12,
"children": []
}
]
}
]
},
{
"node": 2,
"text": "Grapes",
"parent": null,
"children": [
{
"node": 21,
"text": "Mango",
"parent": 2,
"children": []
}
]
},
{
"node": 3,
"text": "banana",
"parent": null,
"children": []
}
]
See this related Q&A for advice on how to absract makeTree and makeIndex into their own modules. The techniques explained here will help you understand the basics for how and why we design programs in this way. If you have any questions, please ask.