Search code examples
javascriptarraysnestedtraversal

Sort array of nested objects into separate array of arrays


I have an array:

[ 
  { id: 1, 
    name: "parent1", 
    children: [ 
               { id: 10, 
                 name: "first_child_of_id_1", 
                 children: [ 
                            { id: 100, name: "child_of_id_10", children: []},
                            { id: 141, name: "child_of_id_10", children: []}, 
                            { id: 155, name: "child_of_id_10", children: []}
                           ]
               },
               { id: 42, 
                 name: "second_child_of_id_1", 
                 children: [ 
                            { id: 122, name: "child_of_id_42", children: []},
                            { id: 133, name: "child_of_id_42", children: []}, 
                            { id: 177, name: "child_of_id_42", children: []}
                           ]
               }
             ]
  },
  { id: 7, 
    name: "parent7", 
    children: [ 
               { id: 74, 
                 name: "first_child_of_id_7", 
                 children: [ 
                            { id: 700, name: "child_of_id_74", children: []},
                            { id: 732, name: "child_of_id_74", children: []}, 
                            { id: 755, name: "child_of_id_74", children: []}
                           ]
               },
               { id: 80, 
                 name: "second_child_of_id_7", 
                 children: [ 
                            { id: 22, name: "child_of_id_80", children: []},
                            { id: 33, name: "child_of_id_80", children: []}, 
                            { id: 77, name: "child_of_id_80", children: []}
                           ]
               }
             ]
  }
] 

What I need is an array of arrays like this:

[
  [ "id", "name", "parent_id", "parent_name" ],
  [  1, "parent1", null, "" ],
  [ 10, "first_child_of_id_1", 1, "parent1"],
  [ 42, "second_child_of_id_1", 1, "parent1"],
  [100, "child_of_id_10", 10, "first_child_of_id_1"]
]

and so on for all nested objects for me to convert them into CSV rows. I've checked many answers and found a similar problem here: How to convert array of nested objects to CSV? But it produces too long rows for many nested objects and I am not experienced enough with JavaScript to modify map function.

const categories = [ 
                { id: 1, 
                name: "parent1", 
                children: [ 
                            { id: 10, 
                            name: "first_child_of_id_1", 
                            children: [ 
                                        { id: 100, name: "child_of_id_10", children: []},
                                        { id: 141, name: "child_of_id_10", children: []}, 
                                        { id: 155, name: "child_of_id_10", children: []}
                                        ]
                            },
                            { id: 42, 
                            name: "second_child_of_id_1", 
                            children: [ 
                                        { id: 122, name: "child_of_id_42", children: []},
                                        { id: 133, name: "child_of_id_42", children: []}, 
                                        { id: 177, name: "child_of_id_42", children: []}
                                        ]
                            }
                        ]
                },
                { id: 7, 
                name: "parent7", 
                children: [ 
                            { id: 74, 
                            name: "first_child_of_id_7", 
                            children: [ 
                                        { id: 700, name: "child_of_id_74", children: []},
                                        { id: 732, name: "child_of_id_74", children: []}, 
                                        { id: 755, name: "child_of_id_74", children: []}
                                        ]
                            },
                            { id: 80, 
                            name: "second_child_of_id_7", 
                            children: [ 
                                        { id: 22, name: "child_of_id_80", children: []},
                                        { id: 33, name: "child_of_id_80", children: []}, 
                                        { id: 77, name: "child_of_id_80", children: []}
                                        ]
                            }
                        ]
                }
            ] 


    function pivot(arr) {
        var mp = new Map();

        function setValue(a, path, val) {
            if (Object(val) !== val) { // primitive value
                var pathStr = path.join('.');
                var i = (mp.has(pathStr) ? mp : mp.set(pathStr, mp.size)).get(pathStr);
                a[i] = val;
            } else {
                for (var key in val) {
                    setValue(a, key == '0' ? path : path.concat(key), val[key]);
                }
            }
            return a;
        }

        var result = arr.map(obj => setValue([], [], obj));
        return [[...mp.keys()], ...result];
    }


    function toCsv(arr) {
        return arr.map(row =>
            row.map(val => isNaN(val) ? JSON.stringify(val) : +val).join(',')
        ).join('\n');
    }
<button onclick="console.log(toCsv(pivot(categories)))">Output</button>


Solution

  • Simple DFS or BFS algorithm should do the job here. The difference is the order of created "rows".
    If you want to have all children of given node listed immediately after their parent, then you need to use BFS.

    Example with DFS and BFS:

    const input = [{
            id: 1,
            name: "parent1",
            children: [{
                    id: 10,
                    name: "first_child_of_id_1",
                    children: [{
                            id: 100,
                            name: "child_of_id_10",
                            children: []
                        },
                        {
                            id: 141,
                            name: "child_of_id_10",
                            children: []
                        },
                        {
                            id: 155,
                            name: "child_of_id_10",
                            children: []
                        }
                    ]
                },
                {
                    id: 42,
                    name: "second_child_of_id_1",
                    children: [{
                            id: 122,
                            name: "child_of_id_42",
                            children: []
                        },
                        {
                            id: 133,
                            name: "child_of_id_42",
                            children: []
                        },
                        {
                            id: 177,
                            name: "child_of_id_42",
                            children: []
                        }
                    ]
                }
            ]
        },
        {
            id: 7,
            name: "parent7",
            children: [{
                    id: 74,
                    name: "first_child_of_id_7",
                    children: [{
                            id: 700,
                            name: "child_of_id_74",
                            children: []
                        },
                        {
                            id: 732,
                            name: "child_of_id_74",
                            children: []
                        },
                        {
                            id: 755,
                            name: "child_of_id_74",
                            children: []
                        }
                    ]
                },
                {
                    id: 80,
                    name: "second_child_of_id_1",
                    children: [{
                            id: 22,
                            name: "child_of_id_80",
                            children: []
                        },
                        {
                            id: 33,
                            name: "child_of_id_80",
                            children: []
                        },
                        {
                            id: 77,
                            name: "child_of_id_80",
                            children: []
                        }
                    ]
                }
            ]
        }
    ]
    
    //DFS
    function deepWalk(node, parent, output = []) {
        if (!node || typeof node !== 'object' || !node.id) return;
    
        output.push([node.id, node.name, parent ? parent.id : null, parent ? parent.name : ""])
        if (node.children) {
            for (const child of node.children) {
                deepWalk(child, node, output);
            }
        }
    
        return output;
    }
    
    //BFS
    function broadWalk(root) {
        const output = []
        const queue = [];
        queue.push({
            node: root,
            parent: null
        });
        while (queue.length) {
            const {
                node,
                parent
            } = queue.shift();
            output.push([node.id, node.name, parent ? parent.id : null, parent ? parent.name : ""])
            if (node.children) {
                for (const child of node.children) {
                    queue.push({
                        node: child,
                        parent: node
                    });
                }
            }
        }
        return output;
    }
    
    
    
    
    let rowsDfs = [
        ["id", "name", "parent_id", "parent_name"]
    ];
    let rowsBfs = [
        ["id", "name", "parent_id", "parent_name"]
    ];
    for (const node of input) {
        rowsDfs = [...rowsDfs, ...deepWalk(node)];
        rowsBfs = [...rowsBfs, ...broadWalk(node)];
    
    }
    
    console.log("rows DFS: ", rowsDfs)
    console.log("rows BFS: ", rowsBfs)