Reputation: 45
I receive an array of posts through an API and want to merge the ones with the same "month" and "year" (day is not important), into one object. I looked up for answers but there are just too many foo-bar examples that confuses more than helping. I want to know the cleanest, most elegant way of handling such problems, without getting into call-back hell and nested blocks...
Here is the API response:
0:
{
date: {day: 27, month: 1, year: 2020}
id: 3
}
1:
{
date: {day: 28, month: 1, year: 2020}
id: 4
}
2:
{
date: {day: 31, month: 1, year: 2020}
id: 5
}
3:
{
date: {day: 1, month: 2, year: 2020}
id: 6
}
4:
{
date: {day: 2, month: 2, year: 2020}
id: 7
}
The expected outcome:
0:
result: {month: 1, year: 2020, id:[3,4,5]}
1:
result: {month: 2, year: 2020, id:[6,7]}
Upvotes: 1
Views: 745
Reputation: 676
Here is an alternate approach, if you are not a big fan of Array.reduce
and Array.values
and also, if you like to consider performance when running the response for a larger data set.
This approach avoids cloning object (or rather non-mutating object) with spread operator i.e {...<anyObject>}
while iterating. which should be fine for minimal set of data but but definitely not when you deal with huge volume.
const response = [{
date: { day: 27, month: 1, year: 2020 },
id: 3
}, {
date: { day: 28, month: 1, year: 2020 },
id: 4
}, {
date: { day: 31, month: 1, year: 2020 },
id: 5
},{
date: { day: 1, month: 2, year: 2020 },
id: 6
},{
date: { day: 2, month: 2, year: 2020 },
id: 7
}];
function groupByMonthYear(response) {
// output
const groupedData = []
// Using map for lookup to avoid iterating again on the grouped data
const referenceMap = new Map();
// destructing month, year and id from the response
for (const { date: { month, year }, id } of response) {
const groupKey = `${month}${year}`
// check if the month and year reference is already seen using the groupKey MMYYYY
if (referenceMap.has(groupKey)) {
referenceMap.get(groupKey).id.push(id);
// early return
continue;
}
// simply add a new entry if it doesn't exist
const data = {
month,
year,
id: [id]
};
groupedData.push(data);
referenceMap.set(groupKey, data)
}
return groupedData;
}
// Invoke and Print the result
console.log(groupByMonthYear(response));
Upvotes: 1
Reputation: 30390
One approach would be to use the Array#reduce()
method to transform the input array into a dictionary, where each value contains the accumulation of id's for that month and year. Once this dictionary has been built, you could then extract the values of that dictionary to an array via Object#values()
to obtain the required output:
let input=[{date:{day:27,month:1,year:2020},id:3},{date:{day:28,month:1,year:2020},id:4},{date:{day:31,month:1,year:2020},id:5},{date:{day:1,month:2,year:2020},id:6},{date:{day:2,month:2,year:2020},id:7}];
/* Convert the dictionary that will be created by reduce to a value array */
var output = Object.values(input.reduce((dict, item) => {
const { date, id } = item;
/* The distinct key for this item based on month/year of date field */
const key = `${date.month}-${date.year}`;
/* Check if dictionary already has an object value for key. This short hand
will only insert a new object value for key, if one does not already exist
in the dictionary */
const value = dict[key] || { month : date.month, year : date.year, id : [] };
/* Add the item id to the dictionary entries id array */
value.id.push(id);
/* Update value object for key */
return { ...dict, [key] : value };
}, {}))
console.log(output);
The idea here is that the dictionary is built using Compound Keys, where the keys are derived from the month and year of the current array item.
When no value exists for the current key, a new value object is inserted to the dictionary for that key:
{ month : date.month, year : date.year, id : [] }
The id
of the current array item is then added (accumulated) to the id
sub array of the object for that key:
dict[key].id.push(id);
Hope that helps
Upvotes: 2