Recently, I needed a way to ensure that my JavaScript array of objects does not contain any duplicate objects based on an ‘id’ key. While I do enjoy more common approaches to this solution with the use of a simple ‘for’ loop it is always a bit of fun to see how folks have come up with modern solutions.
It wasn’t too long before I stumbled across this ES6 one-liner:
1 |
let uniqueObjArray = [...new Map(objArray.map((item) => [item["id"], item])).values()]; |
Pretty neat, huh?
Here are two locations I found them:
But as many code oneliners are, they are a little tricky to understand. So I wanted to spend some time understanding how this worked and thought, you might find this a little interesting too.
Let’s take a look at an example and then work through each bit as we go.
The Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
const myObjArray = [ { name: "Eva Devore", character: "Evandra", episodes: 15, }, { name: "Alessia Medina", character: "Nixie", episodes: 15, }, { name: "Kendall Drury", character: "DM", episodes: 15, }, { name: "Thomas Taufan", character: "Antrius", episodes: 14, }, { name: "Alessia Medina", character: "Nixie", episodes: 15, }, ]; // Creates an array of objects with unique "name" property values. let uniqueObjArray = [ ...new Map(myObjArray.map((item) => [item["name"], item])).values(), ]; console.log("uniqueObjArray", uniqueObjArray); // LOGS: // uniqueObjArray [ // { name: 'Eva Devore', character: 'Evandra', episodes: 15 }, // { name: 'Alessia Medina', character: 'Nixie', episodes: 15 }, // { name: 'Kendall Drury', character: 'DM', episodes: 15 }, // { name: 'Thomas Taufan', character: 'Antrius', episodes: 14 } // ] |
In the example above, we want to create a unique array of objects based on the ‘name’ property of each object set.
You can see that there is a duplicate property in positions 1 and 4 with key ‘name’ and value, ‘Alessia Medina’ that we need to remove.
You can also change the key to either the ‘character’ or ‘episodes’ property.
When the distinct array of objects is created it will set the last duplicate object as the object value. Why? Because the script will essentially reassign the ‘name’ property each time it loops through the array when creating the new map.
Let’s start breaking down the script so we can see how each one operates.
Code Breakdown
myObjArray.map
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Creates a new 2d array with the selected key in position 0 and the object in position 1 of each iteration. let test_uniqueObjArray_map = myObjArray.map((item) => { return [item["name"], item]; }); // LOGS: test_uniqueObjArray_map // [ // ["Eva Devore", { name: "Eva Devore", character: "Evandra", episodes: 15 }], // ["Alessia Medina",{ name: "Alessia Medina", character: "Nixie", episodes: 15 }], // ["Kendall Drury", { name: "Kendall Drury", character: "DM", episodes: 15 }], // ["Thomas Taufan", { name: "Thomas Taufan", character: "Antrius", episodes: 14 }], // ["Alessia Medina",{ name: "Alessia Medina", character: "Nixie", episodes: 15 }] // ]; |
The first task of this script remaps the array of objects using the JavaScript map() method. This method takes a function, which in our is an arrow function.
Map method arrow functions generally look like this:
1 |
myArray.map((element) => <em>return new element as a result of a callback</em>) |
As an ordinary function, it would look like this:
1 2 3 |
myArray.map(function(element){ return <em>new element as a result of a callback</em> }) |
In our example above, we have our callback arguments on a new line so we will also need to include curly braces {}
.
With the map method, the function will act on each array and return the result to generate a new array of the same length.
For us, our call back condition rebuilds each array to make a sub-array containing the value of each name key in the array as the zeroeth element and the object at the first element.
1 2 3 4 5 |
... [item["name"], item]; ... |
So the first element in the new array will look like this:
1 2 3 4 5 |
... ["Eva Devore", { name: "Eva Devore", character: "Evandra", episodes: 15 }] ... |
new Map
A quick side example
Before we continue, let’s take a quick look at a basic Map process on a 2d array:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Creates a key-value map of a 2d array let valuesObject = new Map([ ["key_one", "val_one"], ["key_two", "val_two"], ["key_three", "val_three"], ]); console.log("valuesObject", valuesObject); // LOGS: valuesObject Map { // 'key_one' => 'val_one', // 'key_two' => 'val_two', // 'key_three' => 'val_three' // } |
To be frank, I didn’t really understand the Map object too well until I explored this script.
Map object stores key-value pairs similar to an Object. However, the Map maintains the insertion order of the properties. You’ll see Map objects often displayed like this when logged out in the console.
1 |
'key' => 'value' |
Map can be iterated through in a similar way to a 2d array with the zeroeth element as a key and the next element as a value for each property of the map – ['key', 'value']
.
Alternatively, we can also generate a Map from a 2d array as we did in the example above – turning each sub-array into a key-value pair.
Back to our main example…
new Map of our example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// PREVIOUS LOG: test_uniqueObjArray_map // [ // ["Eva Devore", { name: "Eva Devore", character: "Evandra", episodes: 15 }], // ["Alessia Medina",{ name: "Alessia Medina", character: "Nixie", episodes: 15 }], // ["Kendall Drury", { name: "Kendall Drury", character: "DM", episodes: 15 }], // ["Thomas Taufan", { name: "Thomas Taufan", character: "Antrius", episodes: 14 }], // ["Alessia Medina",{ name: "Alessia Medina", character: "Nixie", episodes: 15 }] // ]; // Create a new Map from our mapped data. let test_uniqueObjArray_NewMap = new Map(test_uniqueObjArray_map); // LOGS: test_uniqueObjArray_NewMap Map { // 'Eva Devore' => { name: 'Eva Devore', character: 'Evandra', episodes: 15 }, // 'Alessia Medina' => { name: 'Alessia Medina', character: 'Nixie', episodes: 15 }, // 'Kendall Drury' => { name: 'Kendall Drury', character: 'DM', episodes: 15 }, // 'Thomas Taufan' => { name: 'Thomas Taufan', character: 'Antrius', episodes: 14 } // } |
We are using the data we retrieved from our previous example here to remove some of the clutter from the process. I have added those results at the top of the code block above.
In this example, we simply apply new Map
to this array of data. By doing this Map turns into a type of Object with a key-value pair. Now keep in mind that Object keys are the highlander of data types – there can be only one.
What does this mean beyond a bad joke that really shows my age?
It means that each key must be unique. All of our keys are now the names of our users. The new Map constructor process will then iterate through each name and store it and then assign its value. If a key already exists it will overwrite it with this next value with the same key name.
This means that the last duplicate key will always be displayed. Effectively only storing unique values.
Displaying the keys of each property in the Map
We can generate iterators to go through each key or value with the keys()
and values()
methods respectively.
We will have a look at the keys() method first quickly.
Let’s apply keys()
to our test_uniqueObjArray_NewMap Map
we generated above.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// PREVIOUS LOG: test_uniqueObjArray_NewMap Map { // 'Eva Devore' => { name: 'Eva Devore', character: 'Evandra', episodes: 15 }, // 'Alessia Medina' => { name: 'Alessia Medina', character: 'Nixie', episodes: 15 }, // 'Kendall Drury' => { name: 'Kendall Drury', character: 'DM', episodes: 15 }, // 'Thomas Taufan' => { name: 'Thomas Taufan', character: 'Antrius', episodes: 14 } // } console.log("test_uniqueObjArray_NewMap_keys", test_uniqueObjArray_NewMap_keys); // LOGS: test_uniqueObjArray_NewMap_keys [Map Iterator] { // 'Eva Devore', // 'Alessia Medina', // 'Kendall Drury', // 'Thomas Taufan' // } |
As you can see this produces an iterator of all the (unique) keys in our data as a Map Iterator. It’s not quite an array of objects, but it allows us to iterate over each key to do something with it.
The same is true for the values()
method.
Displaying the values of each property in the Map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// PREVIOUS LOG: test_uniqueObjArray_NewMap Map { // 'Eva Devore' => { name: 'Eva Devore', character: 'Evandra', episodes: 15 }, // 'Alessia Medina' => { name: 'Alessia Medina', character: 'Nixie', episodes: 15 }, // 'Kendall Drury' => { name: 'Kendall Drury', character: 'DM', episodes: 15 }, // 'Thomas Taufan' => { name: 'Thomas Taufan', character: 'Antrius', episodes: 14 } // } let test_uniqueObjArray_NewMap_values = test_uniqueObjArray_NewMap.values(); console.log( "test_uniqueObjArray_NewMap_values", test_uniqueObjArray_NewMap_values ); // LOGS: test_uniqueObjArray_NewMap_values [Map Iterator] { // { name: 'Eva Devore', character: 'Evandra', episodes: 15 }, // { name: 'Alessia Medina', character: 'Nixie', episodes: 15 }, // { name: 'Kendall Drury', character: 'DM', episodes: 15 }, // { name: 'Thomas Taufan', character: 'Antrius', episodes: 14 } // } |
Here we want to get an iterator of our values so that we can recreate an array of objects again.
Using the values() iterator method we now have our Map values ready to go.
Using the spread syntax to create our array of object
Now that we have an iterator of our unique values we can now place them in our spread syntax – “...
“.
When you apply the spread syntax on an array, it will add each item of an iterable to the array. Take a look at what it does to our Map values.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// PREVIOUS LOG: test_uniqueObjArray_NewMap_values [Map Iterator] { // { name: 'Eva Devore', character: 'Evandra', episodes: 15 }, // { name: 'Alessia Medina', character: 'Nixie', episodes: 15 }, // { name: 'Kendall Drury', character: 'DM', episodes: 15 }, // { name: 'Thomas Taufan', character: 'Antrius', episodes: 14 } // } let test_uniqueObjArray_NewMap_values_asArray = [ ...test_uniqueObjArray_NewMap_values, ]; // LOGS: test_uniqueObjArray_NewMap_values_asArray [ // { name: 'Eva Devore', character: 'Evandra', episodes: 15 }, // { name: 'Alessia Medina', character: 'Nixie', episodes: 15 }, // { name: 'Kendall Drury', character: 'DM', episodes: 15 }, // { name: 'Thomas Taufan', character: 'Antrius', episodes: 14 } // ] |
This is similar to using the Array.from()
static method that would look like this:
1 2 3 |
let test_uniqueObjArray_NewMap_values_asArray = Array.from( test_uniqueObjArray_NewMap_values ); |
Performance
So how does this one-liner stack up against a more traditional for-loop like this?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
var array = [ { name: "Eva Devore", character: "Evandra", episodes: 15, }, { name: "Alessia Medina", character: "Nixie", episodes: 15, }, { name: "Kendall Drury", character: "DM", episodes: 15, }, { name: "Thomas Taufan", character: "Antrius", episodes: 14, }, { name: "Alessia Medina", character: "Nixie", episodes: 15, }, ];; var unique = []; var distinct = []; for( let i = 0; i < array.length; i++ ){ if( !unique[array[i].name]){ distinct.push(array[i].name); unique[array[i].name] = 1; } } console.log(distinct) |
Surprisingly better than I thought it would, to be honest.
Running a benchmark test with jsbench.me, the one-liner ran only 13.74% slower. Which is pretty good compared to some of the other options I found out there.
Conclusion
So should you be using this oneliner over the for
loop? Is an impressive one-liner better than something more clear? To be honest, I am on the fence.
I do like the way this script operates. It is clean and once I got my head around the Map object, it did make a lot of sense. I think if I saw something like this in the wild I could pretty easily identify what it was for and see that it was a nice short solution to a problem.
I don’t think I would use this approach when I need to iterate over objects in the many thousands. Then speed becomes important. But if I need something in my toolkit to solve a problem like this, then I am definitely going to use it.
I have an example of how I used the code to check for duplicate selections of files in Google Drive here:
https://yagisanatode.com/2021/06/27/creates-a-google-workspace-add-on-file-picker-card-with-cardservice-that-opens-a-google-picker-in-an-overlay-window-google-apps-script/
What do you think? Is it too abstract or is it elegant?
Did this tutorial help you understand the JavaScript one-liner better? Do you think you would apply it in your own projects?
Please let me know your thoughts in the comments below. I really enjoy hearing how things are used in the wild.