Search code examples
javascriptarraysmergemaps

How to merge two arrays of objects of the same kind and operate on a certain field?


I have two arrays of objects of the same structure. Array 1 contains elements such as:

[
        {
            id: "1",
            name: "XX",
            displayName: "XX",
            count: 12
        },
        {
            id: "2",
            name: "XX",
            displayName: "XX",
            count: 12
        },
        {
            id: "3",
            name: "XX",
            displayName: "XX",
            count: 12
        }
    ]

Array 2 contains elements such as:

[
        {
            id: "1",
            count: 2
        },
        {
            id: "3",
            count: 5
        }
    ]

I need a resulting array of this type:

[
        {
            id: "1",
            name: "XX",
            displayName: "XX",
            count: 10
        },
        {
            id: "2",
            name: "XX",
            displayName: "XX",
            count: 12
        },
        {
            id: "3",
            name: "XX",
            displayName: "XX",
            count: 7
        }
    ]

That means I need a new array containing all items from array1 with that same structure, but if in array 2 i have a matching ID the new count should be the difference between the value of the two, otherwise it is unchanged. I have been trying to implement this using .reduce() but I am having some trouble getting the logic together, can anyone shed some light on how should I think this through? I am fairly new to JS and I come from a mostly C99 and Python background.

I am excluding the use of nested for loops for this for obvious reasons. A solution I had in mind was to make all the "count" values in in the second array negative, and using this other method I found on this same website. This solution also implies all attributes are int values and sums them all up:

        const sumItem = ({ id, ...a }, b) => ({
            id,
            ...Object.keys(a)
                .reduce((r, k) => ({ ...r, [k]: a[k] + b[k] }), {})
            });
        

        const sumObjectsByKey = (...arrs) => [...
            [].concat(...arrs) // combine the arrays
                .reduce((m, o) => // retuce the combined arrays to a Map
                    m.set(o.id, // if add the item to the Map
                        m.has(o.id) ? subItem(m.get(o.id), o) : { ...o } // if the item exists in Map, sum the current item with the one in the Map. If not, add a clone of the current item to the Map
                    )
                    , new Map).values()]

But that does not feel elegant or "right", and I feel like I should instead focus on understanding methods related to maps a bit better. Can anybody help?


Solution

  • Reduce to an intermediate object and take the values of that:

    const array1 = [{
      id: "1",
      name: "XX",
      displayName: "XX",
      count: 12
    }, {
      id: "2",
      name: "XX",
      displayName: "XX",
      count: 12
    }, {
      id: "3",
      name: "XX",
      displayName: "XX",
      count: 12
    }];
    
    const array2 = [{
      id: "1",
      count: 2
    }, {
      id: "3",
      count: 5
    }];
    
    const result = Object.values([...array1, ...array2].reduce((a, v) => {
      if (a[v.id]) a[v.id].count -= v.count
      else a[v.id] = { ...v }
      return a;
    }, {}));
    
    console.log(result);