How to find duplicate values in a JavaScript array of objects, and output only unique values?

77,058

Solution 1

You could use a Set in combination with Array#map and a spread operator ... in a single line.

Map returns an array with all names, which are going into the set initializer and then all values of the set are returned in an array.

var family = [{ name: "Mike", age: 10 }, { name: "Matt", age: 13 }, { name: "Nancy", age: 15 }, { name: "Adam", age: 22 }, { name: "Jenny", age: 85 }, { name: "Nancy", age: 2 }, { name: "Carl", age: 40 }],
    unique = [...new Set(family.map(a => a.name))];

console.log(unique);

For filtering and return only unique names, you can use Array#filter with Set.

var family = [{ name: "Mike", age: 10 }, { name: "Matt", age: 13 }, { name: "Nancy", age: 15 }, { name: "Adam", age: 22 }, { name: "Jenny", age: 85 }, { name: "Nancy", age: 2 }, { name: "Carl", age: 40 }],
    unique = family.filter((set => f => !set.has(f.name) && set.add(f.name))(new Set));

console.log(unique);

Solution 2

The Solution

Store occurrences of name external to the loop in an object, and filter if there's been a previous occurrence.

https://jsfiddle.net/nputptbb/2/

var occurrences = {}

var filteredFamily = family.filter(function(x) {
  if (occurrences[x.name]) {
    return false;
  }
  occurrences[x.name] = true;
  return true;
})

you can also generalize this solution to a function

function filterByProperty(array, propertyName) {
  var occurrences = {}

  return array.filter(function(x) {
    var property = x[propertyName]
    if (occurrences[property]) {
      return false;
    }
    occurrences[property]] = true;
    return true;
  })
}

and use it like

var filteredFamily = filterByProperty(family, 'name')

Explanation

Don't compare objects using indexOf, which only uses the === operator between objects. The reason why your current answer doesn't work is because === in JS does not compare the objects deeply, but instead compares the references. What I mean by that you can see in the following code:

var a = { x: 1 }
var b = { x: 1 }

console.log(a === b) // false
console.log(a === a) // true

Equality will tell you if you found the same exact object, but not if you found an object with the same contents.

In this case, you can compare your object on name since it should be a unique key. So obj.name === obj.name instead of obj === obj. Moreover another problem with your code that affects its runtime and not its function is that you use an indexOf inside of your reduce. indexOf is O(n), which makes the complexity of your algorithm O(n^2). Thus, it's better to use an object, which has O(1) lookup.

Solution 3

This will work fine.

const result = [1, 2, 2, 3, 3, 3, 3].reduce((x, y) => x.includes(y) ? x : [...x, y], []);

console.log(result);

Solution 4

I just thought of 2 simple ways for Lodash users

Given this array:

let family = [
  {
    name: "Mike",
    age: 10
  },
  {
    name: "Matt",
    age: 13
  },
  {
    name: "Nancy",
    age: 15
  },
  {
    name: "Adam",
    age: 22
  },
  {
    name: "Jenny",
    age: 85
  },
  {
    name: "Nancy",
    age: 2
  },
  {
    name: "Carl",
    age: 40
  }
]

1. Find duplicates:

let duplicatesArr = _.difference(family, _.uniqBy(family, 'name'), 'name')

// duplicatesArr:
// [{ 
//   name: "Nancy", 
//   age: 2 
// }]

2 Find if there are duplicates, for validation purpose:

let uniqArr = _.uniqBy(family, 'name')

if (uniqArr.length === family.length) {
    // No duplicates
}

if (uniqArr.length !== family.length) {
    // Has duplicates
}

Solution 5

With the code you mentioned, you can try:

family.filter((item, index, array) => {
  return array.map((mapItem) => mapItem['name']).indexOf(item['name']) === index
})

Or you can have a generic function to make it work for other array of objects as well:

function printUniqueResults (arrayOfObj, key) {
  return arrayOfObj.filter((item, index, array) => {
    return array.map((mapItem) => mapItem[key]).indexOf(item[key]) === index
  })
}

and then just use printUniqueResults(family, 'name')

(FIDDLE)

Share:
77,058
Rico Letterman
Author by

Rico Letterman

Updated on July 09, 2022

Comments

  • Rico Letterman
    Rico Letterman almost 2 years

    I'm learning JS. Supposing I have the below array of objects:

    var family = [
      {
        name: "Mike",
        age: 10
      },
      {
        name: "Matt"
        age: 13
      },
      {
        name: "Nancy",
        age: 15
      },
      {
        name: "Adam",
        age: 22
      },
      {
        name: "Jenny",
        age: 85
      },
      {
        name: "Nancy",
        age: 2
      },
      {
        name: "Carl",
        age: 40
      }
    ];
    

    Notice that Nancy is showing up twice (changing only the age). Supposing I want to output only unique names. How do I output the above array of objects, without duplicates? ES6 answers more than welcome.

    Related (couldn't find a good way for usage on objects):

    EDIT Here's what I tried. It works well with strings but I can't figure how to make it work with objects:

    family.reduce((a, b) => {
      if (a.indexOf(b) < 0 ) {
        a.push(b);
      }
      return a;
    },[]);
    
  • m0meni
    m0meni over 7 years
    This is so unnecessarily, overly complex
  • Dave Cousineau
    Dave Cousineau over 7 years
    @AR7 complex? I wrote it in like 5 minutes. It just looks ugly because that's how encapsulation works in javascript.
  • Dave Cousineau
    Dave Cousineau over 7 years
    @AR7 I've remove the namespace pattern which was unnecessary
  • m0meni
    m0meni over 7 years
    If you're going to include ES6 Sets, why not also ES6 classes since that would encapsulate your code just as well as your namespace pattern did. Now it just looks weird lol.
  • Dave Cousineau
    Dave Cousineau over 7 years
    @AR7 I'm in the middle of learning ES6. not a fan of ES6 classes yet and I can't just code one up. (Actually I've switched to typescript.)
  • m0meni
    m0meni over 7 years
    I added one in for you. Not sure why I'm so invested in this question lol. Sorry for downvoting you. Good night!
  • Rico Letterman
    Rico Letterman over 7 years
    Is there a way to output my array of objects instead of this flat array?
  • Nina Scholz
    Nina Scholz over 7 years
    what about your question I want to output only unique names.? if not, which one would you like to get the first object, or the last?
  • Dave Cousineau
    Dave Cousineau over 7 years
    feel free to let me know if there's some problem with this, not sure what the downvotes are for
  • Dinesh Rajan
    Dinesh Rajan about 7 years
    its probably because your fiddle write to the console...was running the fiddle, looking at o/p, and wondering anything worked...not really bothering to check the code first (and see its writing to console)
  • Charles Wood
    Charles Wood almost 7 years
    I really appreciate this answer -- it's cross-browser, performant, and easy-to-read.
  • Neo
    Neo almost 7 years
    Very well done. I like the two options you thoroughly explained.
  • jose
    jose over 6 years
    hi @Nina Scholz it's working fine but not work i IE11 give anyother solution
  • Nina Scholz
    Nina Scholz over 6 years
    @jose, this question is marked ES6, and IE11 does not support it. you could use a hash table instead with an object and collect all names there.
  • jose
    jose over 6 years
    any relavant example @Nina Scholz
  • Nina Scholz
    Nina Scholz over 6 years
    @jose, you could use something like this: stackoverflow.com/questions/40482541/…
  • Zum Dummi
    Zum Dummi over 5 years
    can you tell me what is the X do and the [spreadd x : y ] ? and the last empty array also [ ] ?
  • ronoc4
    ronoc4 about 5 years
    So if theres a duplicate of the names, but you want the ages all stored in an age array. So the name nacey and age 2 is gone but what if you could add the age 2 to the object nacey that's left over. And you could have the nacey object age [15, 2]