Search code examples
javascriptloopsobjectnested-loopsreduce

Looping through and reducing nested array


I can't seem to get my head around the array.reduce() function. I've got the following array:

orders = [{
    "id": 4930,
    "status": "pending",
    "line_items": [
        {
            "item_name": "Crepe",
            "item_qty": 2,
            "recipe": [
                {
                    "ing_name": "Flour",
                    "ing_qty": "120",
                    "ing_unit": "g"
                },
                {
                    "ing_name": "Milk",
                    "ing_qty": "100",
                    "ing_unit": "ml"
                },
                {
                    "ing_name": "Egg",
                    "ing_qty": "2",
                    "ing_unit": "each"
                }
            ]
        },
        {
            "item_name": "Pancake",
            "item_qty": 3,
            "recipe": [
                {
                    "ing_name": "Flour",
                    "ing_qty": "120",
                    "ing_unit": "g"
                },
                {
                    "ing_name": "Milk",
                    "ing_qty": "100",
                    "ing_unit": "ml"
                },
                {
                    "ing_name": "Egg",
                    "ing_qty": "2",
                    "ing_unit": "each"
                },
                {
                    "ing_name": "Sugar",
                    "ing_qty": "10",
                    "ing_unit": "g"
                }
            ]
        }
    ]
},
{
    "id": 4927,
    "status": "pending",
    "line_items": [
        {
            "item_name": "Pancake",
            "item_qty": 2,
            "recipe": [
                {
                    "ing_name": "Flour",
                    "ing_qty": "120",
                    "ing_unit": "g"
                },
                {
                    "ing_name": "Milk",
                    "ing_qty": "100",
                    "ing_unit": "ml"
                },
                {
                    "ing_name": "Egg",
                    "ing_qty": "2",
                    "ing_unit": "each"
                },
                {
                    "ing_name": "Sugar",
                    "ing_qty": "10",
                    "ing_unit": "g"
                }
            ]
        }
    ]
}];

and I'm trying to get a result like below. I'm not sure how to go about multiplying the ingredient amounts with the item quantities:

"total_ingredients": [{
    "ing_name": "Flour",
    "ing_qty": "840",
    "ing_unit": "g"
  },
  {
    "ing_name": "Milk",
    "ing_qty": "700",
    "ing_unit": "ml"
  },
  {
    "ing_name": "Egg",
    "ing_qty": "14",
    "ing_unit": "each"
  },
  {
    "ing_name": "Sugar",
    "ing_qty": "20",
    "ing_unit": "g"
  }
];

I've tried following the structure that was given here but the arrow function is throwing me off. Any sort of help would be greatly appreciated.


Solution

  • Read about reduce. It's awesome. About this one, let me explain.

    We start with this skeleton, preparing to group by some property:

    orders.reduce(function(agg, order) {
      agg[order["some_property"]] = order;
      return agg;
    }, {})
    

    That's the idea!

    Anyway, for each of these, we iterate the list of recipes and products, with the idea to collect instead of "some_property" the actual "ing_name". While we group, we calculate the total. Finally, Object.values() will remove the keys we grouped by and turn it into the required array.

    var orders = [{
        "id": 4930,
        "status": "pending",
        "line_items": [{
            "item_name": "Crepe",
            "item_qty": 2,
            "recipe": [{
                "ing_name": "Flour",
                "ing_qty": "120",
                "ing_unit": "g"
              },
              {
                "ing_name": "Milk",
                "ing_qty": "100",
                "ing_unit": "ml"
              },
              {
                "ing_name": "Egg",
                "ing_qty": "2",
                "ing_unit": "each"
              }
            ]
          },
          {
            "item_name": "Pancake",
            "item_qty": 3,
            "recipe": [{
                "ing_name": "Flour",
                "ing_qty": "120",
                "ing_unit": "g"
              },
              {
                "ing_name": "Milk",
                "ing_qty": "100",
                "ing_unit": "ml"
              },
              {
                "ing_name": "Egg",
                "ing_qty": "2",
                "ing_unit": "each"
              },
              {
                "ing_name": "Sugar",
                "ing_qty": "10",
                "ing_unit": "g"
              }
            ]
          }
        ]
      },
      {
        "id": 4927,
        "status": "pending",
        "line_items": [{
          "item_name": "Pancake",
          "item_qty": 2,
          "recipe": [{
              "ing_name": "Flour",
              "ing_qty": "120",
              "ing_unit": "g"
            },
            {
              "ing_name": "Milk",
              "ing_qty": "100",
              "ing_unit": "ml"
            },
            {
              "ing_name": "Egg",
              "ing_qty": "2",
              "ing_unit": "each"
            },
            {
              "ing_name": "Sugar",
              "ing_qty": "10",
              "ing_unit": "g"
            }
          ]
        }]
      }
    ];
    
    total_ingredients =
      Object.values(orders.reduce(function(agg, order) {
        order.line_items.forEach(function(item) {
          item.recipe.forEach(function(product) {
            agg[product.ing_name] = agg[product.ing_name] || {
              ing_name: product.ing_name,
              ing_qty: 0,
              ing_unit: product.ing_unit
            }
            agg[product.ing_name].ing_qty = Number(agg[product.ing_name].ing_qty) + Number(product.ing_qty)
          })
        })
        return agg;
      }, {}))
    
    console.log({
      total_ingredients: total_ingredients
    })