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.