One fairly simple technique looks like this:
const nestComments = (root, xs) =>
xs .filter (({parent}) => parent == root)
.map (({id, parent, ...rest}) => ({id, ...rest, comments: nestComments (id, xs)}))
const comments = [{id: 5819, parent: 0, content: "Waar vind ik meer?"}, {id: 5820, parent: 5819, content: "Epic site!"}, {id: 5821, parent: 5819, content: "Stoinks."}, {id: 5822, parent: 5821, content: "Can I get a what now?"}, {id: 5823, parent: 0, content: "Yeeeee"}]
console .log (nestComments (0, comments))
.as-console-wrapper {max-height: 100% !important; top: 0}
I didn't build this in isolation, though. I actually built it by using the technique described below and then inserting the function parameters back into the above.
I would actually in practice use the version below, or some variant of it over this custom function, as it lets me write this code in terms of simpler functions. But YMMV.
Not long ago, I wrote an answer offering a generic approach to these sorts of problems (see the "Update: Generalization" section). There's a lot more information about it there, but essentially this forest
function takes a flat list, two functions, and a representation of the root node, and returns this sort of nested structure. The first function details how we build our current node given an original node and a function that will yield the children when we pass it a representation of the original node. The second function simply reports, when given a representation of the current node and a candidate child node, whether the second is in fact a child of the first.
// forest :: ([a], (a, (c -> [b])) -> b), ((c, a) -> Bool), c) -> [b]
const forest = (xs, build, isChild, root) =>
xs .filter (x => isChild (root, x))
.map (node => build (node, root => forest (xs, build, isChild, root)))
const comments = [{id: 5819, parent: 0, content: "Waar vind ik meer?", comments: []}, {id: 5820, parent: 5819, content: "Epic site!", comments: []}, {id: 5821, parent: 5819, content: "Stoinks.", comments: []}, {id: 5822, parent: 5821, content: "Can I get a what now?", comments: []}, {id: 5823, parent: 0, content: "Yeeeee", comments: []}]
const nested = forest (
comments,
({id, parent, ...rest}, handleKids) => ({id, ...rest, comments: handleKids (id)}),
(id, {parent}) => parent == id,
0
)
console .log (nested)
.as-console-wrapper {max-height: 100% !important; top: 0}
The second function, isChild
is fairly trivial here. We want to know if the second node is a child of the one represented by our id. We can just use (id, {parent}) => parent == id
. Our root
is represented by 0
, and of course we're just going to pass a list of comments as xs
. So the only thing to figure out is the build
parameter, one which, if we have a node and a function, handleKids
, will take its id and give us a array of children, how do we build the output we want? It's not that complicated. We simply copy over the important parts of our node, and add a comments
node which is the result of calling the function with the current node's id. That looks like ({id, parent, ...rest}, handleKids) => ({id, ...rest, comments: handleKids (id)})
I chose to remove the now-unnecessary parent
node. But if you wanted to keep it, it can simply be left out of the parameter destructuring, e.g. ({id, ...rest}, handleKids) => ({id, ...rest, comments: handleKids (id)})