Search code examples
javascriptangularloopslodashangular6

Nested tree Structure Data Generation


please bare with my English and writing question, i have a array of json data which i am trying to make it in a nested tree structure but not getting success , hope someone can help me out. My Example Data:

[ {
  "id" : 1,
  "name" : "Abc",
  "path" : "/",
  "type" : "folder"
}, {
  "id" : 2,
  "name" : "Xyz",
  "path" : "/Abc/",
  "type" : "folder"
}, {
  "id" : 3,
  "name" : "Pqr",
  "path" : "/Abc/Xyz/",
  "type" : "folder"
}, {
  "id" : 4,
  "name" : "Zap", 
  "path" : "/Abc/Xyz/Pqr/",
  "type" : "folder"
 },{
  "id" : 5,
  "name" : "file1", 
  "path" : "/Abc/Xyz/Pqr/",
  "type" : "file"
},{
  "id" : 6,
  "name" : "file2", 
  "path" : "/Abc/Xyz/Pqr/",
  "type" : "file"
},{
  "id" : 7,
  "name" : "file3", 
  "path" : "/Abc/Xyz/",
  "type" : "file"
},{
  "id" : 8,
  "name" : "file4", 
  "path" : "/Abc/Xyz/Pqr/Zap/",
  "type" : "file"
}

Sorry i am taking little big data to make understand properly, now the nested format i which i want it is something like this.

[{
  "id" : 1,
  "name" : "Abc",
  "path" : "/",
  "type" : "folder"
  "Children":[{
    "id" : 2,
    "name" : "Xyz",
    "path" : "/Abc/",
    "type" : "folder",
    "Children":[{
           "id" : 3,
           "name" : "Pqr",
           "path" : "/Abc/Xyz/",
           "type" : "folder",
           "Children": [{
                   "id" : 4,
                    "name" : "Zap", 
                    "path" : "/Abc/Xyz/Pqr/",
                    "type" : "folder",
                    "Children":[{
                            "id" : 8,
                             "name" : "file4", 
                              "path" : "/Abc/Xyz/Pqr/Zap/",
                             "type" : "file"
                         }]
                   },{
                    "id" : 5,
                    "name" : "file1", 
                    "path" : "/Abc/Xyz/Pqr/",
                    "type" : "file"
                   },{
                      "id" : 6,
                      "name" : "file2", 
                      "path" : "/Abc/Xyz/Pqr/",
                      "type" : "file"
             }]
         },{
           "id" : 7,
           "name" : "file3", 
           "path" : "/Abc/Xyz/", 
           "type" : "file"
      }]
 }]
 }

Now the login using with lodash is like this: datas = allData

 const dd= [];
_.forEach(datas, function(v, k) {
  let cc = {};
    if (v.type == 'folder') {

      cc['children'] = _.filter(datas, function(v1, k1) {
        if (v.path + v.name + '/' == v1.path || v1.path.startsWith(v.path + v.name + '/')) {
          return v1;
        }
      });
      cc['name'] = v.name;
      cc['type'] = v.type;
      cc['id'] = v.id;
      cc['path'] = v.path;
      dd.push(cc);
    } else {
      if (v.path == '/') {
        dd.push(cc);
      }
    }

});

But not getting it properly, i know question got too long but help me out with this.


Solution

  • Whew! That was fun.

    const data = [ {
      "id" : 1,
      "name" : "Abc",
      "path" : "/",
      "type" : "folder"
    }, {
      "id" : 2,
      "name" : "Xyz",
      "path" : "/Abc/",
      "type" : "folder"
    }, {
      "id" : 3,
      "name" : "Pqr",
      "path" : "/Abc/Xyz/",
      "type" : "folder"
    }, {
      "id" : 4,
      "name" : "Zap", 
      "path" : "/Abc/Xyz/Pqr/",
      "type" : "folder"
     },{
      "id" : 5,
      "name" : "file1", 
      "path" : "/Abc/Xyz/Pqr/",
      "type" : "file"
    },{
      "id" : 6,
      "name" : "file2", 
      "path" : "/Abc/Xyz/Pqr/",
      "type" : "file"
    },{
      "id" : 7,
      "name" : "file3", 
      "path" : "/Abc/Xyz/",
      "type" : "file"
    },{
      "id" : 8,
      "name" : "file4", 
      "path" : "/Abc/Xyz/Pqr/Zap/",
      "type" : "file"
    }]
    
    const pathPartRegex = /.*?\//g;
    const tree = _.reduce(data, (result, value) => {
        const pathParts = value.path.match(pathPartRegex);
        let node = result;
        let path = "";
    
        // Go down through tree until last path part
        const notLastPart = pathParts.splice(0, pathParts.length - 1);
        for (const pathPart of notLastPart) {
            path += pathPart;
            const existingNode = node.children 
                                    ? node.children.find(item => item.path === path)
                                    : node.find(item => item.path === path);
            if (existingNode) {
                node = existingNode
            } else {
                // If we need to traverse over a path that doesn't exist, just create it
                // See notes 
                const newNode = {
                    path: path,
                    children: []
                };
    
                // The root element is just an array, and doesn't have a children property
                if (node.children) {
                    node.children.push(newNode);
                } else {
                    node.push(newNode);
                }
                node = newNode;
            }
        }
    
        // Add new node
        const newNode = {
            id: value.id,
            name: value.name,
            type: value.type,
            path: value.path,
            children: []
        };
    
        // The root element is just an array, and doesn't have a children property
        if (node.children) {
            node.children.push(newNode);
        } else {
            node.push(newNode);
        }
    
        return result;
    }, []);
    

    Tested via RunKit (https://npm.runkit.com/lodash)


    Notes:

    The sample data set does not cover how to handle a situation where the "parent" path is not defined at all:

    const data = [{
      "id" : 1,
      "name" : "Abc",
      "path" : "/",
      "type" : "folder"
    }, {
      "id" : 3,
      "name" : "Pqr",
      "path" : "/Abc/Xyz/",
      "type" : "folder"
    }];
    

    Nor a situation where the "parent" path is defined after the child:

    const data = [{
      "id" : 1,
      "name" : "Abc",
      "path" : "/",
      "type" : "folder"
    }, {
      "id" : 3,
      "name" : "Pqr",
      "path" : "/Abc/Xyz/",
      "type" : "folder"
    }, {
      "id" : 2,
      "name" : "Xyz",
      "path" : "/Abc/",
      "type" : "folder"
    }];
    

    The code I wrote will handle these, but may create nodes without an id property. If you need to handle situations like that, you can either fix the input data beforehand or modify this code to handle those situations.