'Why does Javascript Set not do unique objects?

Sets are supposed to contain unique objects, but it doesn't work for objects in javascript.

var set = new Set()
<- undefined
set.add({name:'a', value: 'b'})
<- Set {Object {name: "a", value: "b"}}
set.add({name:'a', value: 'b'})
<- Set {Object {name: "a", value: "b"}, Object {name: "a", value: "b"}}

It works for primitives

var b = new Set()
<- undefined
b.add(1)
<- Set {1}
b.add(2)
<- Set {1, 2}
b.add(1)
<- Set {1, 2}

So how do I get it to work with objects? I get the fact that they are different objects with the same values, but I'm looking for like a deep unique set.

EDIT:

Here's what I'm actually doing

    var m = await(M.find({c: cID}).populate('p')) //database call
    var p = new Set();
    m.forEach(function(sm){
        p.add(sm.p)
    })

This is to get a unique list of sm.p



Solution 1:[1]

well, if you are looking for a deep unique set, you can make a deep set by youself, by extending the original 'Set', like this:

function DeepSet() {
    //
}
DeepSet.prototype = Object.create(Set.prototype);
DeepSet.prototype.constructor = DeepSet;
DeepSet.prototype.add = function(o) {
    for (let i of this)
        if (deepCompare(o, i))
            throw "Already existed";
    Set.prototype.add.call(this, o);
};

Solution 2:[2]

Another option is you could use JSON.stringify() to keep your object unique, that way it is comparing against a string instead of an object reference.

set.add(JSON.stringify({name:'a', value: 'b'}))

and then after everything is formatted you can just parse those lines back to an array like this:

const formattedSet = [...set].map(item) => {
  if (typeof item === 'string') return JSON.parse(item);
  else if (typeof item === 'object') return item;
});

Solution 3:[3]

Based on Joe Yichong post, here a suggestion to extend Set for TypeScript.

export class DeepSet extends Set {

  add (o: any) {
    for (let i of this)
      if (this.deepCompare(o, i))
        return this;
    super.add.call(this, o);
    return this;
  };

  private deepCompare(o: any, i: any) {
    return JSON.stringify(o) === JSON.stringify(i)
  }
}

Solution 4:[4]

You can use Maps. If you need unique values based on the key. For me I wanted to have unique selection from array of object

let cats = [
    { category: 6 },
    { brand: 'purina' },
    { category: 5 },
    { category: 5 },
    { brand: 'purina' }];

var myMap = new Map();

cats.forEach(object => {
    for (const key in object) {
      myMap.set(key, object[key])
    }
})
console.log(myMap) //Map { 'category' => 5, 'brand' => 'purina' }

Here category and brand isn't repeated

Solution 5:[5]

Well, Javascript Set don't store unique object because every object will have their own references. Thus, Set will store the unique values only so it treats even two or more object have the same values their references are different.

Example -

var set = new Set();

set.add({ id: 1 });
set.add({ id: 2 });
set.add({ id: 3 });

// Every object has different reference
var d = { id: 4 };

set.add(d); 
set.add(d);

for (const item of set) {
    console.log(item);
}

// Output : 1 2 3 4

Note : The solution is you can create a reference for the same object and add into Set as above example I have done.

Solution 6:[6]

If you can afford the “expense” of using a library, in this case fast-deep-equal (it’s a dependency on thousands of other packages—many staple ones—so it’s likely already in your graph) then there’s a dead simple extension to Set: UniqueSet

It’s basically the same as Joe Yichong’s answer except it’s exported as a module and implements fast-deep-equal to compare each element deeply, so it can be an easy and convenient approach. You could also just write it yourself—it does nothing special nor opaque.

Using fast-deep-equal is more reliable than JSON serialization due to issues with certain data types and sorting, some of which have been mentioned here already.

Solution 7:[7]

A common scenario is when we're getting some data from API, which repeats some of the events.

Being in a for loop we need to use JSON.stringify({...}) when using push(...) on the sampleArray:

for (const id in response.data) {
  sampleArray.push(JSON.stringify({id, ...});
}

Then we can create a Set from the sampleArray:

const sampleSet = new Set(sampleArray);

Now, convert back to an Array:

const list = Array.from(sampleSet);

For each of the elements JSON.parse(...) to have it in the right format again (note: I badly needed the same sampleArray, you can use a new variable):

sampleArray = [];
list.forEach((element) => {
  JSON.parse(element);
  sampleArray.push(JSON.parse(element));
});

Full code:

for (const id in response.data) {
  sampleArray.push(JSON.stringify({id, ...});
}

const sampleSet = new Set(sampleArray)
const list = Array.from(sampleSet);

sampleArray = [];
list.forEach((element) => {
  JSON.parse(element)
  sampleArray.push(JSON.parse(element))
});

Will work fine for that scenario, because we've control over the order, which is crucial, as noted by @dankuck.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Joe Yichong
Solution 2 Dhia Djobbi
Solution 3 felix_teg
Solution 4 Samiullah Khan
Solution 5 anandchaugule
Solution 6 sepiariver
Solution 7 Daniel Danielecki