0

I am pretty new to react/ES6 and I got a strange behavior when I use reduce method to transform a flat array I receive from an API into a nested object. I was aleready familiar with map and filter but never used reduce before.

I am pretty sure that I am using it wrong but it goes like this.

I have the folowing flat dataset:

[
  {
    category:"Category1",
    subCategory:"SubCategory1",
    productType:"ProductType1",
    brand:"brand1"
  },
  {
    category:"Category1",
    subCategory:"SubCategory2",
    productType:"ProductType2",
    brand:"brand2"
  },
  {
    category:"Category1",
    subCategory:"SubCategory2",
    productType:"ProductType2",
    brand:"brand3"
  },
  {
    category:"Category2",
    subCategory:"SubCategory1",
    productType:"ProductType1",
    brand:"brand1"
  },
  {
    category:"Category2",
    subCategory:"SubCategory1",
    productType:"ProductType2",
    brand:"brand2"
  }
]

I am using the folowing code to get a nested object from the flat dataset.

getNestedData() {
  var baseData = this.getBaseData();

  return baseData.reduce((categories,category) => {
    if(!categories[category.category]){
      categories[category.category] = {
        category: category.category,
        subCategories: baseData.filter(bd => bd.category === category.category)
          .reduce((subCategories,subCategory) => {
            if(!subCategories[subCategory.subCategory]) {
              subCategories[subCategory.subCategory] = {
                subCategory: subCategory.subCategory,
                productTypes: baseData.filter(bd => bd.category === subCategory.category && bd.subCategory === subCategory.subCategory)
                  .reduce((productTypes,productType) => {
                    if(!productTypes[productType.productType]) {
                      productTypes[productType.productType] = {
                        productType: productType.productType,
                        brands: baseData.filter(bd => bd.category === productType.category && bd.subCategory === productType.subCategory && bd.productType === productType.productType)
                          .reduce((brands,brand) => {
                            if(!brands[brand.brand]) {
                              brands[brand.brand] = {
                                brand: brand.brand
                              }
                            }
                            return brands;
                          }) 
                      };
                    }
                    return productTypes;
                  })
              };
            }
            return subCategories;
          })
      }
    }
    return categories;
  });
}

However, the result isn't what I expected. It returns the root array object inside the first category of the array. Like Category1 => [Category1,Category2]. And if you go under the category1 child the same thing happen.

I prepared the following jsFiddle to show my issue.

Let me know what I am doing wrong I googled this for hours. I would also like to know if this way is the most efficient one?

Edit:

There is an example of the expected output:

[
  {
    category: "Category1"
    subCategories:[
      {
        subCategory:"SubCategory1",
        productTypes:[
          {
            productType:"ProductType1",
            brands:["Brand1"]    
          }
        ]   
      },
      {
        subCategory:"SubCategory2",
        productTypes:[
          {
            productType:"ProductType2",
            brands:["Brand2","Brand3"]
          }   
        ]
      }
    ]
  },
  {
    category: "Category2",
    subCategories:[
      {
        subCategory:"SubCategory1",
        productTypes:[
          {
            productType:"ProductType1",
            brands:["Brand1"]
          },
          {
            productType:"ProductType2",
            brands:["Brand2"]
          }
        ]
      }
    ]
  }
]

Edit

Mat was right I forgot to pass the initial value when I was using reduce. Passing the initial value made reduce work as I expected. I have then been able to change my code to get the expected result. The code isn't the most elegant but works very well:

getNestedData()
{
    var baseData = this.getBaseData();
  return baseData
  .map(c => {return c.category;})
  .reduce((categories,category) => {
  var currentCategory = categories.filter(
    c => c.category === category
  );
    if (currentCategory.length === 0) {
            var categoryData = baseData.filter(bd => bd.category === category);
      categories.push({
        category:category,
        subCategories: categoryData
        .map(c => {return c.subCategory})
        .reduce((subCategories,subCategory) => {
            var currentSubCategory = subCategories.filter(sc => sc.subCategory === subCategory)
            if(currentSubCategory.length === 0)
          {
            var subCategoryData = categoryData.filter(cd => cd.subCategory === subCategory);
            subCategories.push({
                subCategory:subCategory,
              productTypes: subCategoryData
              .map(scd => {return scd.productType})
              .reduce((productTypes,productType) => {
                var currentproductType = productTypes.filter(pt => pt.productType === productType);
                if(currentproductType.length === 0)
                {
                    var productTypeData = subCategoryData.filter(scd => scd.productType === productType)
                  productTypes.push({
                    productType:productType,
                    brands: productTypeData
                    .map(ptd => {return ptd.brand})
                    .reduce((brands,brand) => {
                        var currentBrand = brands.filter(b => b.brand === brand);
                      if(currentBrand.length === 0)
                      {
                        brands.push({brand:brand});
                      }
                      return brands;
                    },[])
                  });
                }
                return productTypes;
              },[])
            });
          }
          return subCategories;
        },[])
      });
    }
    return categories;
  },[])
}

There is the working jsFiddle with the expected behavior.

3
  • 5
    Can you please show an example of the expected output? Commented Dec 13, 2018 at 2:26
  • update You question with expected result otherwise it will be nominated as "unclear what asking", learn to explain Your needs correctly Commented Dec 13, 2018 at 2:36
  • There t is let me know if this is clear enough Commented Dec 13, 2018 at 3:21

1 Answer 1

2

So it looks like you are trying to denormalise your array data. Your heavily nested function is difficult to read, and I think you could accomplish what you want much more easily if you use lodash's set function. For example:

const data = [
  {
    category:"Category1",
    subCategory:"SubCategory1",
    productType:"ProductType1",
    brand:"brand1"
  },
  {
    category:"Category1",
    subCategory:"SubCategory2",
    productType:"ProductType2",
    brand:"brand2"
  },
  {
    category:"Category1",
    subCategory:"SubCategory2",
    productType:"ProductType2",
    brand:"brand3"
  },
  {
    category:"Category2",
    subCategory:"SubCategory1",
    productType:"ProductType1",
    brand:"brand1"
  },
  {
    category:"Category2",
    subCategory:"SubCategory1",
    productType:"ProductType2",
    brand:"brand2"
  }
]

const result = data.reduce((output, item) => {
  _.set(output, `${item.category}.subCategories.${item.subCategory}.productTypes.${item.productType}.brands.${item.brand}.brand`, item.brand)
  return output
}, {})

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

The reason why your code wasn't working (ignoring the typos), is that I think you were forgetting to provide an initial value to the reduce function. Look at the last optional argument here.

Sign up to request clarification or add additional context in comments.

3 Comments

I will try it tomorrow morning. Thanks! I added an expected output to my original post. Does your solution will give me this output?
There's a button right there that says "Run code snippet". Look for yourself
No my solution does not give you the output you updated your question with. It was impossible to infer that you wanted sub types to be arrays. This makes the solution more difficult than what I provided.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.