I have two arrays of objects like below:
array1 = [
{ material: "ABC123", cost: 100 },
{ material: "DEF456", cost: 150 }
]
array2 = [
{ material: "ABC123", date: "1/1/20", quantity: 4 },
{ material: "ABC123", date: "1/15/20", quantity: 1 },
{ material: "ABC123", date: "2/15/20", quantity: 3 },
{ material: "ABC123", date: "4/15/21", quantity: 1 },
{ material: "DEF456", date: "3/05/20", quantity: 6 },
{ material: "DEF456", date: "3/18/20", quantity: 1 },
{ material: "DEF456", date: "5/15/21", quantity: 2 }
]
I'd like to create a new array of objects that includes all key/value pairs from array1 along with the aggregated quantity for each item by year and month.
The result would be:
[
ABC123: {
cost: 100,
byYear: {
2020: {
byMonth: {
1: 5,
2: 3
}
}
},
{
2021: {
byMonth: {
4: 1
}
}
},
},
DEF456: {
cost: 150,
byYear: {
2020: {
byMonth: {
3: 7,
2: 3
}
}
},
{
2021: {
byMonth: {
5: 2
}
}
},
}
]
Below is my code so far, using lodash but that's not required in the solution. The issue I'm encountering is that the year and month keys created under each item aren't specific to that item. Each item is getting year and month keys that exist for all items.
let itemSummary = {};
_.forEach(array1, function (item) {
itemSummary[item["material"]] = itemSummary[item["material"]] || {}; // create material as key
itemSummary[item["material"]] = item; // add props from array1 under each key
_.forEach(array2, function (trans) {
// iterate through transactions and aggregate by year and month
let transactionYear = new Date(trans["date"]).getFullYear();
let transactionMonth = new Date(trans["date"]).getMonth() + 1;
itemSummary[item["material"]]["byYear"] = itemSummary[item["material"]]["byYear"] || {}; //create year key
itemSummary[item["material"]]["byYear"][transactionYear] = itemSummary[item["material"]]["byYear"][transactionYear] || {}; // set year key
itemSummary[item["material"]]["byYear"][transactionYear]["byMonth"] = itemSummary[item["material"]]["byYear"][transactionYear]["byMonth"] || {};
itemSummary[item["material"]]["byYear"][transactionYear]["byMonth"][transactionMonth] =
itemSummary[item["material"]]["byYear"][transactionYear]["byMonth"][transactionMonth] || {};
});
});
Obviously, this doesn't aggregate the quantities as I mentioned above as I first need to get the correct year and month keys for each item.
Any assistance is much appreciated
You could take a dynamic approach for all groupings and another object for getting a final sum.
const
array1 = [{ material: "ABC123", cost: 100 }, { material: "DEF456", cost: 150 }],
array2 = [{ material: "ABC123", date: "1/1/20", quantity: 4 }, { material: "ABC123", date: "1/15/20", quantity: 1 }, { material: "ABC123", date: "2/15/20", quantity: 3 }, { material: "ABC123", date: "4/15/21", quantity: 1 }, { material: "DEF456", date: "3/05/20", quantity: 6 }, { material: "DEF456", date: "3/18/20", quantity: 1 }, { material: "DEF456", date: "5/15/21", quantity: 2 }],
groups = [
({ material }) => material,
_ => 'byYear',
({ date }) => '20' + date.split('/')[2],
_ => 'byMonth'
],
sum = {
key: ({ date }) => date.split('/')[0],
value: ({ quantity }) => quantity
},
result = array2.reduce(
(r, o) => {
const temp = groups.reduce((t, fn) => t[fn(o)] ??= {}, r);
temp[sum.key(o)] ??= 0;
temp[sum.key(o)] += sum.value(o);
return r;
},
Object.fromEntries(array1.map(({ material, cost }) => [material, { cost }]))
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If necessary, you could add further grouping and add an array of function for ultimate aggregation.
const
array1 = [{ material: "ABC123", cost: 100 }, { material: "DEF456", cost: 150 }],
array2 = [{ material: "ABC123", date: "1/1/20", quantity: 4 }, { material: "ABC123", date: "1/15/20", quantity: 1 }, { material: "ABC123", date: "2/15/20", quantity: 3 }, { material: "ABC123", date: "4/15/21", quantity: 1 }, { material: "DEF456", date: "3/05/20", quantity: 6 }, { material: "DEF456", date: "3/18/20", quantity: 1 }, { material: "DEF456", date: "5/15/21", quantity: 2 }],
groups = [
({ material }) => material,
_ => 'byYear',
({ date }) => '20' + date.split('/')[2],
_ => 'byMonth',
({ date }) => date.split('/')[0]
],
ultimately = [
(target, { quantity }) => {
target.sum ??= 0;
target.sum += quantity;
},
(target, { quantity }) => {
target.max ??= quantity;
if (quantity > target.max) target.max = quantity;
}
],
result = array2.reduce(
(r, o) => {
const temp = groups.reduce((t, fn) => t[fn(o)] ??= {}, r);
ultimately.forEach(fn => fn(temp, o));
return r;
},
Object.fromEntries(array1.map(({ material, cost }) => [material, { cost }]))
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }