Search code examples
javascriptarraysdata-structuresfiltertree

Convert an array of objects into a deep tree array using Javascript


I have a complex json file that I have to handle with javascript to make it hierarchical. I am trying to convert an array of objects into a deep nested array. There can be any number of divNames with any number of categories and subcategories

Array of objects:

[{
   divName: "ABC",
   divId: 123,
   catName: "XYZ",
   catId: 456,
   subCatName: "PQR"
   subCatId: 781
},
{
   divName: "ABC",
   divId: 123,
   catName: "YQP",
   catId: 281,
   subCatName: "FYI"
   subCatId: 231
},
{
   divName: "ABC",
   divId: 123,
   catName: "XYZ",
   catId: 456,
   subCatName: "YAB"
   subCatId: 587
}
]

Expected output:

[{divName: "ABC",
  divId: 123
  categories: [
     {
       catName: "XYZ".
       catId: 456,
       subCategories: [
         {
            subCatName: "PQR",
            subCatID: 781
         },
         {
            subCatName: "YAB",
            subCatID: 587
         }],
     {
       catName: "YQP"
       catId: 281,
       subCategories: [
         {
           subCatName: "FYI"
           subCatID: 231
         }
       ]
     }]
  ]

This is what I have so far:

nonCompetitorData.map((data, idx) => {
            if(idx === 0) {
                downloadData.push({"divisionName": data.divisionName, "divisionId": data.divisionId});
            } else {
                if (!downloadData[0].categories) {
                    downloadData[0].categories = [];
                    downloadData[0].categories.push({
                        "categoryName": data.categoryName,
                        "categoryId": data.categoryId
                    })
                } else {
                    if(downloadData[0].categories) {
                        if(!downloadData[0].categories.some(c => c.categoryName === data.categoryName)) {
                            downloadData[0].categories.push({
                                "categoryName": data.categoryName,
                                "categoryId": data.categoryId
                            })
                        }
                    }
                    downloadData[0].categories.forEach((cat, i) => {
                        if(!cat.subCategories) {
                            console.log("Categories",downloadData[0].categories[i]);
                            downloadData[0].categories[i].subCategories = [];
                            downloadData[0].categories[i].subCategories.push({
                                "subCategoryName": data.subCategoryName,
                                "subCategoryId": data.subCategoryId
                            });
                        } else {
                            if(cat.subCategories) {
                                if(!cat.subCategories.some(c => c.subCategoryName === data.subCategoryName)) {
                                    downloadData[0].categories[i].subCategories.push({
                                        "subCategoryName": data.subCategoryName,
                                        "subCategoryId": data.subCategoryId
                                    })
                                }
                            }
                        }
                    });
                }
            }
        });

Is there a better way to do this?


Solution

  • You can use Array::reduce and a map to get already added nodes to the tree. If your div, cat, subcat IDs aren't unique together, just add a prefix d for divs and c for cats when accessing the map.

    const r = arr.reduce((map => (r, {divId,divName,catId,catName,subCatId,subCatName}) => {
      const {categories: cats} = map[divId] ??= r[r.length] = {divName, divId, categories:[]};
      (map[catId] ??= cats[cats.length] = {catName, catId, subcategories:[]})
        .subcategories.push({subCatName, subCatId});
      return r;
    })({}), []);
    
    console.log(r);
    <script>
    const arr = [{
       divName: "ABC",
       divId: 123,
       catName: "XYZ",
       catId: 456,
       subCatName: "PQR",
       subCatId: 781
    },
    {
       divName: "ABC",
       divId: 123,
       catName: "YQP",
       catId: 281,
       subCatName: "FYI",
       subCatId: 231
    },
    {
       divName: "ABC",
       divId: 123,
       catName: "XYZ",
       catId: 456,
       subCatName: "YAB",
       subCatId: 587
    }
    ]
    </script>