Search code examples
javascriptarraysrecursionfilter

Javascript - Get all the objects from an array that is direct/nested children of another object


So I have a javascript array that looks like this (simplified). Here beauty (id:1) and health (id:2) is root categories as they have a null value of parentCategoryId. hair care (id:3), hair oil (id:4) and kumarika hair oil (id:5) falls into the beauty category(id:1) as they have parentCategoryId value which directly or indirectly falls into beauty category(see below for explaination).But supplements (id:6) falls into the health category (id:2). How do I get all the categories that (directly or nested) falls into the beauty category (id:1). key parentCategoryId is the identifier of which category directly falls into which category. so here

id:3 has parentCategoryId:1 that means id:3 is direct children of id:1.So falls into beauty category.

id:4 has parentCategoryId:3 that means id:4 is direct children of id:3 which is direct children of id:1.so falls into beauty category.

id:5 has parentCategoryId:4 that means id:5 is direct children of id:4 which is direct children of id:3 which is direct children of id:1 and falls into beauty category.

const categories = [
    {id:1,name:'beauty',parentCategoryId:null},
    {id:2, name:'health', parentCategoryId:null},
    {id:3, name:'hair care', parentCategoryId:1},
    {id:4, name:'hair oil', parentCategoryId:3},
    {id:5, name:'kumarika hair oil', parentCategoryId:4},
    {id:6, name:'supplements', parentCategoryId:2}
]

I have tried recursive function but couldn't find the appropriate output.Say for example I am calling this recursive function with the object that has id:1.

let newCategoriesArray =[]

 const getAllChildCategories = (category) => {
    let childCategories = categories.filter(
      (cat) => cat.parentCategory == category.id
    );
   
    newCategoriesArray.push(...childCategories);
    
    if (childCategories.length > 0) {
      childCategories.map((cat) => {
        getAllChildCategories(cat);
      });
    }
  };

My expected output should be an array of categories that is direct or nested children of beauty (id:1 ). So the new array should look like this.please take into consideration there may exist more nested categorie.

const newCategoriesArray  = [
    {id:3, name:'hair care', parentCategoryId:1},
    {id:4, name:'hair oil', parentCategoryId:3},
    {id:5, name:'kumarika hair oil', parentCategoryId:4},
]

Solution

  • You have some interesting options already, provided categories isn't a very long list (like thousands of entries). (They do lots of looping through the list.)

    If there are a lot of categories in the list (at least thousands), but the hierarchies aren't hundreds deep, I'd probably just loop through categories once, checking the ancestry of each target category as I went.

    I'd start by keeping a map of categories by their ID (just after the categories array, unless that's dynamic; if it's dynamic, create the map on the fly):

    const categoriesById = new Map(
        categories.map((category) => [category.id, category])
    );
    

    Then get the target category ID (since you seem to be starting with a category object) and an array for the results:

    const targetId = category.id;
    const results = [];
    

    Then loop through the categories:

    for (const cat of categories) {
    

    In the loop, we'll go through cat's ancestors (if any) seeing if any of them matches the target:

        // Start with this category in `c`...
        let c = cat;
        // While `c` exists and has a parent...
        while (c && c.parentCategoryId) {
            // Get its parent
            c = c.parentCategoryId && categoriesById.get(c.parentCategoryId);
            // Does the parent match?
            if (c.id === targetId) {
                // Found it
                results.push(cat);
                break;
            }
            // The loop will continue searching ancestry of `cat` via `c`
        }
    

    and that's it!

    Working example (rather shorter without the explanatory comments):

    const categories = [ { id: 1, name: "beauty", parentCategoryId: null }, { id: 2, name: "health", parentCategoryId: null }, { id: 3, name: "hair care", parentCategoryId: 1 }, { id: 4, name: "hair oil", parentCategoryId: 3 }, { id: 5, name: "kumarika hair oil", parentCategoryId: 4 }, { id: 6, name: "supplements", parentCategoryId: 2 }, ];
    const categoriesById = new Map( categories.map((category) => [category.id, category]));
    
    const getAllChildCategories = (category) => {
        const targetId = category.id;
        const results = [];
        for (const cat of categories) {
            let c = cat;
            while (c && c.parentCategoryId) {
                c = c.parentCategoryId && categoriesById.get(c.parentCategoryId);
                if (c.id === targetId) {
                    results.push(cat);
                    break;
                }
            }
        }
        return results;
    };
    
    console.log(getAllChildCategories(categories[0]));
    .as-console-wrapper {
        max-height: 100% !important;
    }