Combine Arrays of Objects and Replace Duplicates

Combine Arrays of Objects and Replace Duplicates

I ran into a situation recently where I was working with an API that only returned one item at a time, but all the items had to be sent back during the update request. That meant that I had to maintain an array containing objects, and give the user the ability to update those objects and add extra objects the array.

Getting Started

Let's look at some examples.

Here's an empty array to start with.

let orders = [];

Then we'll add this array to the original.

const newOrder1 = [
  {
    name: 'Bob',
    food: 'Pizza',
  }
];

There are a few ways to do this. We'll just use concat.

orders = orders.concat(newOrder1);

Now, we have this.

[{ name: 'Bob', food: 'Pizza' }];

If we add a second order...

const newOrder2 = [
  {
    name: 'Sally',
    food: 'Tacos',
  }
];

orders = orders.concat(newOrder2);

Gives us this...

[{ name: 'Bob', food: 'Pizza' }, { name: 'Sally', food: 'Tacos' }];

What happens when we need to update one of the existing orders?

const update = [
  {
    name: 'Bob',
    food: 'Cheeseburgers',
  },
];

Using the same concat method we'll end up adding the update instead of actually replacing the original order.

orders = orders.concat(update);

We get this...

[
  { name: 'Bob', food: 'Pizza' },
  { name: 'Sally', food: 'Tacos' },
  { name: 'Bob', food: 'Cheeseburgers' },
];

That's why we need a way to look at all the objects, and either update if the key already exists, or add a new order if the key doesn't exist.

Solutions

UnionBy

I went with unionBy from lodash to solve the problem. You can add your arrays, and an iteratee to filter by.

const { unionBy } = require('lodash/array');

let orders = [
  {
    name: 'Bob',
    food: 'Pizza',
  }, {
    name: 'Sally',
    food: 'Tacos',
  },
];

const update = [
  {
    name: 'Bob',
    food: 'Cheeseburgers',
  },
];

Notice the order of the arrays. You have to put the update first. Use 'name' for the iteratee.

orders = unionBy(update, orders, 'name');

We replaced Bob's order.

[
  { name: 'Bob', food: 'Cheeseburgers' },
  { name: 'Sally', food: 'Tacos' },
];

JavaScript Map

You can also solve this problem with plain javascript. This won't cover as many conditions as lodash unionBy, but it's good enough for what we're doing here.

In this example we're using Map from JavaScript to create an object with a unique key value pair. This will allow us to overwrite existing objects based on our iteratee, or add them if they don't already exist.

const myUnionBy = (arrays, iteratee) => {
  // create a map
  const map = new Map();

  // iterate the arrays we pass to the function
  arrays.forEach((array) => {
    // iterate the objects in each array
    array.forEach((object) => {
      // set a new key/value pair for each object
      // { 'Bob' => { name: 'Bob', food: 'Pizza' } }
      map.set(object[iteratee], object);
    });
  });

  // return a new array from our map
  return [...map.values()];
};

Group all your arrays inside an array, and make the update last.

orders = myUnionBy([orders, update], 'name');

JavaScript Object

You can actually do the same thing that we did with Map, but using a regular object instead.

const myUnionBy = (arrays, iteratee) => {
  const map = {};

  arrays.forEach((array) => {
    array.forEach((object) => {
      // { Sally: { name: 'Sally', food: 'Tacos' } }
      map[object[iteratee]] = object;
    });
  });

  return Object.values(map);
};

orders = myUnionBy([orders, update], 'name');

Conclusion

We have three ways to join arrays containing objects, and replace duplicates. Probably unionBy from lodash is the easiest, and it covers more conditions than the other examples, but the other two examples are good if you need something simple and don't want to import any packages into your project.