5

How can i use Set() in objects? I read the documentation, it works with values in array, but with objects it do not work. Why? I want to avoid duplicate items that has exacly the same values.

let nameList_one = [{ name: 'harry', id: 1 }, { name: 'john', id: 2 }, { 'name': 'trevor', id: 3  }]
let nameList_two = [{ name: 'harry', id: 1  }, { name: 'ron', id: 4  }, { name: 'jackson', id: 5  }]

let fullNameList = [...nameList_one , ...nameList_two ]

let filteredList = [...new Set(fullNameList)] // the output is the merge of two array, duplicating "harry".

Output:

[{ name: 'harry', id: 1 }, { name: 'john', id: 2 }, { 'name': 'trevor', id: 3 }, { name: 'harry', id: 1 }, { name: 'ron', id: 4 }, { name: 'jackson', id: 5 }]

Expected Output:

[{ name: 'harry', id: 1 }, { name: 'john', id: 2 }, { 'name': 'trevor', id: 3 }, { name: 'ron', id: 4 }, { name: 'jackson', id: 5 }]

Thanks!!

Johnson
  • 966
  • 3
  • 13
  • 29
  • 4
    Two different objects will never be `===` to each other. Object comparison only involves the object's reference value; the property values don't matter. – Pointy Apr 13 '18 at 15:17
  • You would probably need to prototype the function Set() or add a custom comparison method to your object type, as @Pointy said object comparison is done by reference which will not be equal. – Ryan Wilson Apr 13 '18 at 15:18
  • 2
    Relevant info: [How to customize object equality for Javascript Set](https://stackoverflow.com/questions/29759480/how-to-customize-object-equality-for-javascript-set/29759699#29759699) - spoiler: "you can't" so you can't really use a Set for what you're trying to do. It simply doesn't do what you're looking for and can't be customized to do it either. – jfriend00 Apr 13 '18 at 15:21
  • Thanks for explanation! Can you give me a way to solve this? So i understood that `Set()` will not work. – Johnson Apr 13 '18 at 15:22
  • 1
    You can use `reduce`, something like `fullNameList.reduce((a, c) => { !a.find(v => v.id === c.id) && a.push(c); return a; }, []);` – Titus Apr 13 '18 at 15:25
  • Hmm do i have to use a parameter to do the comparison? Just to confirm. =) – Johnson Apr 13 '18 at 15:26
  • 1
    You'll need to decide what makes two objects the same and use that for the comparison inside the `find` function. I assumed that is the `id` property. – Titus Apr 13 '18 at 15:28
  • Got it! Thanks a lot. I will try doing with `reduce` and `find` methods. ;) – Johnson Apr 13 '18 at 15:30

3 Answers3

13

This doesn't work with objects because they are not same objects:

{ name: 'harry', id: 1 } !== { name: 'harry', id: 1 }

A way that may work is:

let filteredList = [...new Set(fullNameList.map(JSON.stringify))].map(JSON.parse);

It cannot be recommended, because it uses serialization where it's unnecessary. Serialization will destroy object instances that cannot be serialized, will ignore non-enumerable properties and won't handle circular references. Objects that have different property order won't be removed as duplicates. Their properties should be sorted first. In case there are nested objects, keys should be sorted recursively. This makes this approach impractical.

If the objects are identified by one or several fields, Map with serialized fields as map keys can be used:

let filteredList = [
  ...new Map(fullNameList.map(obj => [`${obj.id}:${obj.name}`, obj]))
  .values()
];
Estus Flask
  • 150,909
  • 47
  • 291
  • 441
  • 1
    Converting an object to string representation. You're welcome. – Estus Flask Apr 13 '18 at 20:10
  • 1
    we cannot use object to string representation because order of keys may be diffrent – lal rishav Feb 25 '20 at 08:38
  • @lalrishav I agree. I came across the same issue when I was trying to store stringified objects using Set data type in Redis. Same data returned for the same entity from Firebase was not equal when stringified just because the order of the properties was different. – Alper Güven Jan 23 '21 at 14:08
  • 1
    @AlperGüven FWIW, the last time this problem occurred to me with Firebase, I used json-stable-stringify – Estus Flask Jan 23 '21 at 14:35
4

A nice way of doing this is to subclass Set.

class UniqueNameSet extends Set {
    constructor(values) {
        super(values);

        const names = [];
        for (let value of this) {
            if (names.includes(value.name)) {
                this.delete(value);
            } else {
                names.push(value.name);
            }
        }
    }
}

This gives you all the other methods/properties of Set, but adds the extra functionality you are looking for.

lonesomeday
  • 215,182
  • 48
  • 300
  • 305
1

Each object is a pointer to a memory address, so even if the two objects have the same properties it wont be equal. One way to do this would be using JSON.stringify and copare the stringified object.

Douglas
  • 333
  • 3
  • 11