MatiN
MatiN

Reputation: 45

Combine objects of an array with the same date into one - JavaScript

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

Answers (2)

prasana kannan
prasana kannan

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

Dacre Denny
Dacre Denny

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

Related Questions