The other day at work I was faced with what I think is a rather common problem when dealing with data coming from an API.
I was getting from my async http call an array of objects
(addresses in this case), but for unimportant reasons this array was returning objects that could be duplicate (so two identical id
s).
My first instinct was to make a new array, and loop through the addresses' array and do a .findIndex
on each for my "copy". If the "copy" didn't have the address, then I would push it. In the end the code was readable, but I wanted a simpler more straightforward way.
There are many ways to solve this particular problem!
Now that we got that out of the way 😋, I wanted to show this solution that uses the power of JavaScript Set
.
const addresses = [...]; // Some array I got from async call
const uniqueAddresses = Array.from(new Set(addresses.map(a => a.id)))
.map(id => {
return addresses.find(a => a.id === id)
})
Let's dissect this mess:
-
Array.from(new Set())
I'm going to make a new set, and I want to turn it back into an array withArray.from
so that I can later re-map it. -
new Set(addresses.map(a => a.id))
Set will only allow unique values in it, so i'm going to pass it the ids of each object. If the loop tries to add the same value again, it'll get ignored for free. -
.map(id => [...])
With the array of ids I got on step 1, I run a map function on it and return the actual address from the original address array
That's it! Simple, clear, and I had fun learning about/using Set
😎
Huge shout out to my bae Natalia Tepluhina who endured and helped me come up with crazy ideas for this, and her awesome solution to do it with Array.reduce
showcased below ❤️👩💻
const arr = [
{ id: 1, name: "test1" },
{ id: 2, name: "test2" },
{ id: 2, name: "test3" },
{ id: 3, name: "test4" },
{ id: 4, name: "test5" },
{ id: 5, name: "test6" },
{ id: 5, name: "test7" },
{ id: 6, name: "test8" }
];
const filteredArr = arr.reduce((acc, current) => {
const x = acc.find(item => item.id === current.id);
if (!x) {
return acc.concat([current]);
} else {
return acc;
}
}, []);
Latest comments (40)
Man, I only created an account here just for saying thanks. So... thanks! :D
I came here because Chat GPT suggested to me a filter-based solution that did not work as filter does consider two objects unequal due to different spots in memory, eventhough they contain the same values.
You saved me a lot of headaches.
Other solutions...
Leave last appearance:
Leave first appearance:
Remove duplicated names also:
Thanks for chairing, I found this also helpful
Thanks for the solution.
I have two list of objects and I need to remove all duplicates, something like this:
INPUTS
var array1 = [(1, 'banana', 'yellow'), (1, 'apple', 'red'), (1, 'orange', 'orange')];
var array2 = [(1, 'banana', 'yellow'), (1, 'apple', 'red'), (2, 'grapes', 'purple')];
OUTPUT
array1 = [(1, 'orange', 'orange')]
I tried using filter but it doesn't work for objects :(
Thanks for this post
Thank you @marinamosti , you saved my life xD
Thanks so much for your article. It saved and made my day. :)
Really enjoyed this article and the rad discussion!!!!!!
The array reduce solution saved my day!
Tanx Marina
Welcome! :D
what does this bit do? not sure I understand? -> acc.concat([current])
Array.concat is a way to concatenate two arrays into one.
developer.mozilla.org/en-US/docs/W...
let array = [];
let singleEle = [];
const arr = [
{ id: 1, {questionId : { _id: "5e2016a1560d8c2aa842e65d"} }},
{ id: 1, {questionId : { _id: "5e1c211cc201f33834e7baf1"} }},
{ id: 1, {questionId : { _id: "5e201733560d8c2aa842e65e"} }}
];
arr.forEach(item => {
if (array[item.questionId._id] ) {
}
else{
array[item.questionId._id] = true;
singleEle.push(item)
}
});
Thanks! This is exactly what I was looking for
This is great! Thanks
Here's another possibility using the
Map
class constructor andvalues
method:There is a difference between Tony's and Matt's approach in how the final array will look like.
Matt's approach is adding the id for each entry it loops through to a Set and checks whether or not it has been ´seen´ before or not, and return the object if 'no' is the case. So if we look at the returned object with ID: 2, Matt will return the object with name: "test2" as it will consider the next object a duplicate and skip it.
Tony's approach is by creating a new map using ID as a key - which has to be unique - and then extracts the values. E.g.
[1: { id: 1, name: "test1" }, 2: { id: 2, name: "test2" }....]
etc. What this means though, is that even though id: 2 has been added to the map, it is simply overwritten by the third item in the array, thus Tony will return name: "test3" for ID: 2.Just keep this in mind whether you want the first object or the last object by a duplicated identifier to be the truth.
Hi Tony. Your answer helped me, thanks!
BTW, can you please help me achieve this array?
[
{ id: 1, name: "test1" },
{ id: 2, name: "test2" },
{ id: 2, name: "test3" },
{ id: 3, name: "test2" }
]
I mean even if 'id' or 'name' already exists, it should not be omitted because either of the value is different (like in the case of 'name: "test2"') in the whole array.
Hey @mayanxoni ,
Might be late with a reply here, but in your case I would probably map through your array of objects as stringified content, as you are not relying on a single identifier but an entire object.
NB: Though this might not be performant when you are dealing with bigger objects and large arrays.
Hi Mayank,
I'm not sure I follow. If no duplicates are removed then that is just the original array is it not? Or do you mean you want an array with just
id
s 1, 2, and 3? If so, you can use Array.prototype.filter and only returntrue
for theid
s you want to keep.Great
I have to create an account and say thank you! This answer save my day.
wow, i love that
Thanks !!