Merge two n number of array of objects based on a key

I have two arrays:

Array 1:

[
    {old_clicks: 1, date: "2017-01-24" }, 
    { old_clicks: 4, date: "2017-01-22" },
    { old_clicks: 6, date: "2017-01-21" }
]

and array 2:

[
    { total_clicks:120, date: "2017-01-21" },
    { total_clicks: 100, date: "2017-01-19" }
]

I need to merge these two arrays based on date and get this:

[
    { old_clicks: 1,date: "2017-01-24", total_clicks: 0 },
    { old_clicks: 4, date: "2017-01-22", total_clicks: 0 },
    { old_clicks: 6, date: "2017-01-21", total_clicks: 120 },
    { old_clicks: 0, date: "2017-01-19", total_clicks: 100 },
]

I have tried this but this is not working as its not merging properly.

const arr1 = [
    { old_clicks: 1, date: "2017-01-24" }, 
    { old_clicks: 4, date: "2017-01-22" },
    { old_clicks: 6, date: "2017-01-21" }
];


const arr2 =  [
    { total_clicks:120, date: "2017-01-21" },
    { total_clicks: 100, date: "2017-01-19" }
];
        

const mergeById = (a1, a2) => a1.map(itm => ({
    ...a2.find((item) => (item.date=== itm.date)),
    ...itm
}));

console.log(mergeById(arr1,arr2))

23 thoughts on “Merge two n number of array of objects based on a key”

  1. This looks like a case for Array.reduce.

    The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in single output value.

    The signature is:

    arr.reduce(callback( accumulator, currentValue, [, index[, array]] )[, initialValue])
    

    The approach here is to add items using their date property as a key to a "map" object that starts out initialized as an empty object, then summing in the clicks properties you care about. At the end, result will be an object and we can use Object.values() to turn it’s values into an array. We choose to use an object internally here to for faster lookup of existing dates, you could reduce using an array instead, but naively using arr.find would net you a O(n2) solution.

    const arr1 = [
      { old_clicks: 1, date: "2017-01-24" },
      { old_clicks: 4, date: "2017-01-22" },
      { old_clicks: 6, date: "2017-01-21" }
    ];
    
    
    const arr2 = [
      { total_clicks: 120, date: "2017-01-21" },
      { total_clicks: 100, date: "2017-01-19" }
    ];
    
    
    const mergeById = (...arrs) => {
    
      const result = arrs.flat().reduce((acc, obj) => {
        acc[obj.date] = {
          date: obj.date,
          total_clicks: (acc[obj.date]?.total_clicks ?? 0) + (obj?.total_clicks ?? 0),
          old_clicks: (acc[obj.date]?.old_clicks ?? 0) + (obj?.old_clicks ?? 0),
        }
    
        return acc;
      }, {})
      
      return Object.values(result);
    }
    
    console.log(mergeById(arr1, arr2))
    Reply
  2. Here’s a pretty straightforward implementation that uses an object as an intermediary map, and uses Object.assign to handle the property merges.

    This implementation supports an arbitrary amount of input arrays to merge, and also supports any additional properties automatically.

    It’s not tied specifically to your example, it’s a generic solution to the question of how to merge arrays of objects together by key.

    Note that if both arr1 and arr2 define the same property (like old_clicks), the latest one would win. This may or may not be what you want (you might want to add them together?)

    const arr1 = [
        { old_clicks: 1, date: "2017-01-24" },
        { old_clicks: 4, date: "2017-01-22" },
        { old_clicks: 6, date: "2017-01-21" }
    ];
    
    const arr2 = [
        { total_clicks: 120, date: "2017-01-21" },
        { total_clicks: 100, date: "2017-01-19" }
    ];
    
    function mergeByProperty(mergeProperty, defaults, ...arraysToMerge) {
        const mergeObj = {};
    
        arraysToMerge.forEach(function (arrayToMerge) {
            arrayToMerge.forEach(function (item) {
                const mergeValue = item[mergeProperty];
    
                if (mergeValue) {
                    if (!mergeObj[mergeValue]) {
                        mergeObj[mergeValue] = Object.assign({}, defaults, item);
                    }
                    else {
                        Object.assign(mergeObj[mergeValue], item);
                    }
                }
                else {
                    // TODO Invalid item.  Throw an Error or skip it.
                }
            });
        });
    
        return Object.values(mergeObj);
    }
    
    function mergeByDate(...arraysToMerge) {
        return mergeByProperty("date", { old_clicks: 0, total_clicks: 0 }, ...arraysToMerge);
    }
    
    console.log(mergeByDate(arr1, arr2));
    Reply

Leave a Comment