Search code examples
javascriptarraysjavascript-objects

How to groupBy array of objects based on properties in vanilla javascript


How do you groupBy array of objects based on specific properties in vanilla javascript? For example this given:

const products = [
  {
    category: "Sporting Goods",
    price: "$49.99",
    stocked: true,
    name: "Football"
  },
  {
    category: "Sporting Goods",
    price: "$9.99",
    stocked: true,
    name: "Baseball"
  },
  {
    category: "Sporting Goods",
    price: "$29.99",
    stocked: false,
    name: "Basketball"
  },
  {
    category: "Electronics",
    price: "$99.99",
    stocked: true,
    name: "iPod Touch"
  },
  {
    category: "Electronics",
    price: "$399.99",
    stocked: false,
    name: "iPhone 5"
  },
  { category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7" }
];

i want to run a reduce function that would result to a new array of objects like this:

Intended Output:

const categorize = [
  {
    category:"Sporting Goods",
    products: [
      {
        name:"Football",
        price: "$49.99",
        stocked: true
      },
      {
        name:"Baseball",
        price: "$9.99",
        stocked: true
      },
      {
        name:"Basketball",
        price: "$29.99",
        stocked: true
      }
    ]
  },
  {
    category: "Electronics",
    products: [
      {
        name: "iPod Touch",
        price: "$99.99",
        stocked: true
      },
      {
        name: "iPhone 5",
        price: "$399.99",
        stocked: false
      },
      {
        name: "Nexus 7",
        price: "$199.99",
        stocked: true
      }
    ]
  }
]

i based my solution from the tutorial here: https://www.consolelog.io/group-by-in-javascript/ using the reduce function.

Here's my code:

const groupBy = (arr,prop)=>{
  return arr.reduce((groups,item)=>{
    let val = item[prop];
    groups[val] = groups[val]||[];
    groups[val].push(item);
    return groups
  },{});
}

const categorize = groupBy(products,'category');
console.log(categorize);

/* returns an Object like
    Object {Sporting Goods: Array[3], Electronics: Array[3]}
   however it's not the intended output.
*/

I tried to return Object.values(obj) or Object.entries(obj) inside the groupBy function but it just returns an array of 2 arrays like [Array[3],Array[3]] and if i set the initial value (2nd parameter of reduce) to empty [] instead of {}, the output is just an empty array. Need help, thanks!


Solution

  • Because you want an array containing objects (rather than just an array of plain values), create a { category, products: [] } object if it doesn't exist in the accumulator:

    const products=[{category:"Sporting Goods",price:"$49.99",stocked:!0,name:"Football"},{category:"Sporting Goods",price:"$9.99",stocked:!0,name:"Baseball"},{category:"Sporting Goods",price:"$29.99",stocked:!1,name:"Basketball"},{category:"Electronics",price:"$99.99",stocked:!0,name:"iPod Touch"},{category:"Electronics",price:"$399.99",stocked:!1,name:"iPhone 5"},{category:"Electronics",price:"$199.99",stocked:!0,name:"Nexus 7"}];
    
    const output = Object.values(
      products.reduce((a, { category, ...item }) => {
        if (!a[category]) {
          a[category] = { category, products: [] };
        }
        a[category].products.push(item);
        return a;
      }, {})
    );
    console.log(output);