28

I came across this example for creating unique arrays with es6

[ ...new Set(array) ]

Which seems to work fine until I tried it with an array of objects and it didn't return unique array.

i.e.

let item = [ ...new Set([{id:123,value:'test'},{id:123,value:'test'}]) ];

Why is that ?

3
  • What do you mean by "doesnt work"? [ ... new Set([{ val: 1 }, { val: 2 }, { val: 3 }]) ] gives me an array of objects... Commented Oct 12, 2016 at 11:09
  • by Using Set I was hoping the get a filtered Unique array back Commented Oct 12, 2016 at 11:10
  • 8
    This has nothing to do with Set, two objects are never the same, even if they contain the same values Commented Oct 12, 2016 at 11:15

4 Answers 4

27

you can try to do

uniqueArray = a => [...new Set(a.map(o => JSON.stringify(o)))].map(s => JSON.parse(s))

I know its ugly as hell but in most cases works apart from where you have new Date() in your object param then that on stringify be converted to ISO string.

so then do

let arr = [{id:1},{id:1},{id:2}];
uniqueArray(arr) //[{id:1},{id:2}]
Sign up to request clarification or add additional context in comments.

3 Comments

I came across this question and I realised I've written exaclty the same with exactly the same syntax in my console :-)
However, this solution won't work with regexp: a = [{b: /foo/},{b: /bar/}] will return [{b:{}}]
Only this one is woking with Mongoose document.
21

Why is that ?

As per documentation

The Set object lets you store unique values of any type, whether primitive values or object references.

Now reference for each of those arrays inside that Set constructor will be different so they are not considered to be a unique value by the constructor.

Comments

8

This will work:

let objectReference = {id:123,value:'test'}
let uniqueArray = [...new Set([objectReference, objectReference])]

>> [{id:123,value:'test'}]

What you're doing:

let objRef1 = {id:123,value:'test'} // creates a reference to a location in memory
let objRef2 = {id:123,value:'test'} // creates a new reference to a different place in memory

let uniqueArray = [...new Set([objRef1, objRef2])]

>> [{id:123,value:'test'},{id:123,value:'test'}]

Comments

0

If you don't use a library like lodash or radash for example. You can use Set as answered by @Vic. Just a bug detected is about objects without same keys order. For example {a: '1', b: '2'} and {b: '2', a: '1'} are not string equals. Here is a working solution with covered cases

Implementation

uniq(source) {
  if (!Array.isArray(source)) {
     return [];
  }
  return [...new Set(source.map(o => {
    const sortedObjectKeys = Object.keys(o).sort();
    const obj = Object.assign({}, ...sortedObjectKeys.map(k => ({[k]: o[k]})) as any);
    return JSON.stringify(obj);
  }))]
  .map(s => JSON.parse(s));
}

Test cases

describe(`uniq`, () => {
  it('should return unique collection values', () => {
    expect(uniq([{v: 1}, {v: 2}, {v: 1}])).toEqual([{v: 1}, {v: 2}]);
  });

 it('should return unique collection values for unsorted properties', () => {
   expect(uniq([{a: 'test', v: 1}, {v: 2}, {v: 1, a: 'test'}])).toEqual([{a: 'test', v: 1}, {v: 2}]);
 });

 it('should return empty array for non array source', () => {
   expect(uniq({v: 1})).toEqual([]);
   expect(uniq('aString')).toEqual([]);
   expect(uniq(125)).toEqual([]);
   expect(uniq(true)).toEqual([]);
   expect(uniq([])).toEqual([]);
   expect(uniq(undefined)).toEqual([]);
   expect(uniq(null)).toEqual([]);
});

});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.