106

I have an array of objects that looks like this:

var array = [
    {id:123, value:"value1", name:"Name1"},
    {id:124, value:"value2", name:"Name1"},
    {id:125, value:"value3", name:"Name2"},
    {id:126, value:"value4", name:"Name2"}
    ...
];

As you can see, some names are repeated. I want to get a new array with names only, but if some name repeats I don't want to add it again. I want this array:

var newArray = ["Name1", "Name2"];

I'm trying to do this with map:

var newArray = array.map((a) => {
    return a.name;
});

But the problem is that this returns:

newArray = ["Name1", "Name1", "Name2", "Name2"];

How can I set some condition inside map, so it won't return an element that already exists? I want to do this with map or some other ECMAScript 5 or ECMAScript 6 feature.

atw
  • 5,428
  • 10
  • 39
  • 63
snoopy25
  • 1,346
  • 4
  • 12
  • 15
  • 3
    Then remove the duplicates from the array. – Tushar Dec 28 '16 at 14:50
  • 6
    How about a `Set`? https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set – ppasler Dec 28 '16 at 14:51
  • 1
    possible duplicate of [Remove Duplicates from JavaScript Array](http://stackoverflow.com/q/9229645/1048572) – Bergi Dec 28 '16 at 19:56
  • Why does the line containing id:125 not end with a comma? – Peter Mortensen Dec 31 '16 at 13:00
  • Please note that all the answers using .indexOf() will have **poor performance** if the array is large, due to their quadratic [time complexity](https://en.wikipedia.org/wiki/Time_complexity). I would recommend using [ES6 Set](https://stackoverflow.com/a/41364654/99777) or using an [ES5 object](https://stackoverflow.com/a/49024565/99777) as a dictionary. – joeytwiddle Feb 28 '18 at 08:12

17 Answers17

220

With ES6, you could use Set for unique values, after mapping only the names of the objects.

This proposal uses a spread syntax ... for collecting the items in a new array.

const array = [{ id: 123, value: "value1", name:"Name1" }, { id: 124, value: "value2", name: "Name1" }, { id: 125, value: "value3", name: "Name2" }, { id: 126, value: "value4", name: "Name2" }],
      names = [...new Set(array.map(a => a.name))];

console.log(names);
Zanon
  • 29,231
  • 20
  • 113
  • 126
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • 10
    For my info.. what does `...` do? – Rajshekar Reddy Dec 28 '16 at 14:53
  • 10
    @RajshekarReddy, it's a [spread syntax `...`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator) for building an array with an iterable object. – Nina Scholz Dec 28 '16 at 14:56
  • 6
    I'm not upvoting for the "Set" itself, but rather for the clever use of the spread operator. It's always nice to learn something new or to see something new applied to a practical example, so this post deserves an upvote. – briosheje Dec 28 '16 at 14:58
  • 1
    Why throw away the `Set` after you’ve made it? I would argue that `names` should be left as a `Set`—after all, it *is* a set. And `console.log`, at the very least, has no problem with printing it *as* a set. Some libraries may require an array (having not been updated to ES6, or allowing duplicates), but leaving it as a `Set` until it is necessary to use an array saves developer headspace: the runtime itself is keeping track of the fact that `names` is supposed to have unique values, so the developer doesn’t have to remember that. Also, “cleverness” deserves a well-named utility function. – KRyan Dec 28 '16 at 18:13
  • 1
    @KRyan, right, it is better to keep a data structure until the data is finally used, but the question requires an array as result and that's why the spread syntax. – Nina Scholz Dec 28 '16 at 18:17
  • 1
    @NinaScholz Answers are allowed to, and should when appropriate, point out better practices than those requested. I’m not saying you should *remove* the method of achieving an array from the `Set`, just that you should also mention that there may be better approaches. And I do think suggesting a `toArrayFromSet` utility function for the `set => [...set]` would be a good idea, since that syntax is going to be unclear to other developers (at least for now, with ES6 being so new). – KRyan Dec 28 '16 at 18:20
  • 1
    @KRyan I agree that abusing spread syntax is a bad practise here, but there's no point in constructing an `toArrayFromSet` function - it already exists as [`Array.from`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) – Bergi Dec 28 '16 at 19:54
  • @Bergi Ah, perfect: I had checked `Set` for that, forgot to check `Array`. – KRyan Dec 28 '16 at 19:55
  • 1
    @Bergi, please add some sources about the bad practice of using spread syntax with sets. – Nina Scholz Dec 28 '16 at 22:39
  • 36
    It's just my personal opinion, but a [rather common one](http://stackoverflow.com/a/40549565/1048572). Using `Array.from` conveys the intent of conversion much better (and also is easier to understand/search for newbies), spread syntax should be used only where the spreaded element is one of many. Maybe calling it a "bad practise" is a bit too harsh, sorry, I just hope to prevent it from becoming a common idiom. – Bergi Dec 28 '16 at 22:55
  • 4
    @KRyan is ES6 so new? It has been finalized in 2015 june. It's time to start understanding and using the new features – edc65 Dec 29 '16 at 12:54
  • 1
    @edc65 “Start” is the key phrase there. Browser support for it is still incomplete (and older browser's often still need support), which means many, many developers are paying zero attention to it. I would agree that is a mistake, but it’s also a fact. – KRyan Dec 29 '16 at 13:32
66

If you are looking for a JavaScript solution that is not ES 6 (no Set) you can use the Array's reduce method:

var array=[
  {id:123, value:"value1", name:"Name1"},
  {id:124, value:"value2", name:"Name1"},
  {id:125, value:"value3", name:"Name2"},
  {id:126, value:"value4", name:"Name2"}
];
var names = array.reduce(function (a, b) {
  if (a.indexOf(b.name) == -1) {
    a.push(b.name)
  }
  return a;
}, []);

console.log(names);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Dekel
  • 60,707
  • 10
  • 101
  • 129
  • 2
    This answer also has the benefit that it doesn't require intermediate arrays or sets, especially ones containing only the names themselves. (It goes directly from array of objects to array of objects.) +1 – jpmc26 Dec 28 '16 at 20:27
  • @Iwrestledabearonce, isn't this second param the same object as the returned one? – Kaiido Dec 28 '16 at 23:26
  • Yea but it doesnt go straight from one to the other like sort or filter, its rebuilt from an empty array. – I wrestled a bear once. Dec 28 '16 at 23:34
  • filter doesn't act on the original array either, it does create a new array. reduce doesn't : it outputs what you passed in. – Kaiido Dec 29 '16 at 00:15
  • and i guess internally sort and filter build the array one item at time too all i'm saying is it starts with an "intermediate" array and build it into the desired array, effectively the same way you would do with a loop, whereas sort and filter just take a function and return a new array. – I wrestled a bear once. Dec 29 '16 at 03:32
  • 1
    @Dekel — Smaller version :) `var names = array.reduce(function(a, b) { a.indexOf(b.name) === -1 && a.push(b.name) return a; }, []);` – Rayon Jan 06 '17 at 05:10
  • 3
    @Rayon Please don't pay too much attention to the size of the source code, this is a common pitfall for many self made programmers, most importantly, your program should be efficient and readable. –  Nov 28 '17 at 06:46
18

Personally I don't see why everyone is getting all fancy with ES 6. If it were my code I'd prefer to support as many browsers as possible.

var array=[
{id:123, value:"value1", name:"Name1"},
{id:124, value:"value2", name:"Name1"},
{id:125, value:"value3", name:"Name2"},
{id:126, value:"value4", name:"Name2"}
];

   // Create array of unique names
var a = (function(a){
  for (var i = array.length; i--;)
    if (a.indexOf(array[i].name) < 0) a.push(array[i].name);
  return a;
})([]);

console.log(a);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
I wrestled a bear once.
  • 22,983
  • 19
  • 69
  • 116
  • 2
    Won't it be more performant to use an object as a dictionary instead of `indexOf`-ing all the time? E.g. do `added[name] = true` and `if(added[name])` instead of `if(a.indexOf(name))`. – Bojidar Marinov Dec 28 '16 at 17:41
  • @BojidarMarinov depends on the number of items, really – Eric Lagergren Dec 28 '16 at 18:40
  • 8
    "personally i don't see why everyone is getting all fancy with es6" why are you courting a `goto fail`, leaking your index variable into the enclosing scope, etc. etc. ES6 was codified into the standard for good reasons, and is for the most part trivially easy to polyfill/transform for older browsers. – Jared Smith Dec 29 '16 at 02:16
  • If leaking this and that is really an issue its even easier to put it ina closure but at that point you might as well use a polyfill.. its 2016 most people writing javascript are probably not writing enterprise level software – I wrestled a bear once. Dec 29 '16 at 03:07
  • 1
    "i don't see why everyone is getting all fancy with es6": because there are transpilers that convert the code to the old syntax. more readable for the programmer, but equally supported! – This company is turning evil. Dec 29 '16 at 13:02
  • 5
    More readable? Its a its a freaking for loop that pushes items onto an array. Doesnt get much more readable than that...... – I wrestled a bear once. Dec 29 '16 at 13:10
  • Ive added a comment for anyone who has trouble understanding loops and arrays.. and i addressed the comment above like 12 hours before you even up voted it.. – I wrestled a bear once. Dec 29 '16 at 13:44
  • @Iwrestledabearonce. I think the readability in syntactic terms is more or less a wash, and the "functional" version is not any shorter in this instance. The reason people don't use `for` loops anymore and prefer Sets to Arrays has more to do with *correctness*. No fencepost errors, runtime guarantees dedupe, etc. etc. That's not even including the potential `goto fail` still present in your version at the time I write this. – Jared Smith Dec 29 '16 at 14:48
  • There is no goto in javascript so i honestly dont know what youre talking about. Feel free to enlighten me but frankly it will be hard to take it at face value from someone who says "people dont use for loops anymore." You seem to be missing the point of backwards compatibilty which is the whole reason i posted this. – I wrestled a bear once. Dec 29 '16 at 14:53
  • 5
    Quite honestly it just feels like youre trying way too hard to find something to complain about. – I wrestled a bear once. Dec 29 '16 at 14:54
  • @Iwrestledabearonce. You invited people to complain about *your* code by virtue of your (unnecessary) comment about the other answers. Backwards compatibility is a total red herring, other commenters and myself have pointed out that *multiple* code transformers exist. And of course there's no `goto` in JavaScript, I am referring to the famous Apple SSL bug that was introduced by using a conditional without the braces (followed by someone else adding another line to the clause). https://nakedsecurity.sophos.com/2014/02/24/anatomy-of-a-goto-fail-apples-ssl-bug-explained-plus-an-unofficial-patch/ – Jared Smith Jan 04 '17 at 14:20
  • 3
    "I am referring to the famous Apple SSL bug" -- It's not that famous that you can just go and refer to it in random comments out of context and expect people to recognize it. – Hejazzman Jan 05 '17 at 19:31
17

You could also simply combine map with filter

var array = [
  {id:123, value:"value1", name:"Name1"},
  {id:124, value:"value2", name:"Name1"},
  {id:125, value:"value3", name:"Name2"},
  {id:126, value:"value4", name:"Name2"}
];

var unique = array
  .map( item => item.name )
  .filter( ( item, idx, arr ) => arr.indexOf( item ) == idx ) 

console.log(unique)
DavidDomain
  • 14,976
  • 4
  • 42
  • 50
13

You can use Object.keys() to get the array of a given object's own enumerable property names from the object result of iterating array variable with Array.prototype.reduce() where the keys are the destructed names

Code:

const array = [{id:123, value:"value1", name:"Name1"}, {id:124, value:"value2", name:"Name1"}, {id:125, value:"value3", name:"Name2"}, {id:126, value:"value4", name:"Name2"}],
      names = Object.keys(
        array.reduce((a, { name }) => (a[name] = 1, a), {})
      )

console.log(names)
Yosvel Quintero
  • 18,669
  • 5
  • 37
  • 46
9

Many good answers here. I just would like to contribute with some diversity with hopes to give you another perspective.

Arrays are of object type in JavaScript, so they can be used as a hash at the same time. By using this functionality we can greatly simplify the job to be done in a single reduce operation with O(n) time complexity.

If you are not happy with your array holding some properties other than the array keys you might consider keeping a separate hash object as well.

var array = [{id:123, value:"value1", name:"Name1"},
             {id:124, value:"value2", name:"Name1"},
             {id:125, value:"value3", name:"Name2"},
             {id:126, value:"value4", name:"Name2"}
            ],
result = array.reduce((p,c) => p[c.name] ? p : (p[c.name] = true, p.push(c.name), p), []);
console.log(result);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Redu
  • 25,060
  • 6
  • 56
  • 76
  • This ES5 solution has good performance, but using the array for two purposes is confusing, and dangerous if one of the names is just a number. I would recommend doing the `p[c.name]` stuff on a separate object `o` and then discarding it afterwards. – joeytwiddle Feb 28 '18 at 07:59
8

I agree that if you only need the name values, a Set is the way to go.

However, if you want to get an array of unique objects based on the name property, I'd suggest to use a Map. A quick way to create a Map, is via an array of [key, value] arrays:

const array = [{ id: 123, value: "value1", name:"Name1" }, { id: 124, value: "value2", name: "Name1" }, { id: 125, value: "value3", name: "Name2" }, { id: 126, value: "value4", name: "Name2" }],
      unique = new Map(array.map(obj => [obj.name, obj]));

// To get the unique objects
const uniques = Array.from(unique.values());

// Get the names like you already did:
console.log("Names:", uniques.map(obj => obj.name));

// If you ever need the complete array of unique objects, you got a ref:
console.log(JSON.stringify(uniques));
.as-console-wrapper { min-height: 100%; }

An added benefit of Map is that you get both the filter functionality that cuts out the non-uniques, without loosing the connection with the source objects. Of course, it's only needed if you need to reference the unique set of objects multiple times.

user3297291
  • 22,592
  • 4
  • 29
  • 45
4

With ES6 this should do the job.

var array=[
    {id:123, value:"value1", name:"Name1"},
    {id:124, value:"value2", name:"Name1"},
    {id:125, value:"value3", name:"Name2"},
    {id:126, value:"value4", name:"Name2"}
];

var set = new Set();

array.forEach((a)=>{
    set.add(a.name);
}); 

console.log(Array.from(set));
Konstantin Kreft
  • 613
  • 5
  • 18
4

If you're limited to ES5, I would use Lodash's _.uniq

var newArray = _.uniq(array.map(function(a) {
  return a.name;
}));
AndrewHenderson
  • 4,686
  • 3
  • 26
  • 33
2

Using UnderscoreJS,

array = [{id:123, value:"value1", name:"Name1"}, {id:124, value:"value2", name:"Name1"}, {id:125, value:"value3", name:"Name2"}, {id:126, value:"value4", name:"Name2"}];
get_names =  _.pluck(_.uniq(array, 'name'), 'name')
console.log(get_names)
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>

`

Mohideen bin Mohammed
  • 18,813
  • 10
  • 112
  • 118
1

That's how I did it, using a separate empty array.

var array = [
   {id:123, value:"value1", name:"Name1"},
   {id:124, value:"value2", name:"Name1"},
   {id:125, value:"value3", name:"Name2"},
   {id:126, value:"value4", name:"Name2"}     
];

var array2 = []  
  
for (i=0; i<array.length;i++){      
   if (array2.indexOf(array[i].name) == -1){    
     array2.push(array[i].name);
    }
}   

console.log(array2) 
GalAbra
  • 5,048
  • 4
  • 23
  • 42
Abhishek Gurjar
  • 7,426
  • 10
  • 37
  • 45
0

Try this:

nArr = [];
array.forEach((a) => {
    if (nArr.indexOf(a.name) < 0) { 
        nArr.push(a.name); 
    }
}); 
joeytwiddle
  • 29,306
  • 13
  • 121
  • 110
Rafael Gomes Francisco
  • 2,193
  • 1
  • 15
  • 16
0

In ES5, use an object as a dictionary for O(n) performance.

This will only work if all the keys are Strings.

var array = [
    {id: 123, value: "value1", name: "Name1"},
    {id: 124, value: "value2", name: "Name1"},
    {id: 125, value: "value3", name: "Name2"},
    {id: 126, value: "value4", name: "Name2"}
];

var allNames = array.map(item => item.name);

var map = {};
allNames.forEach(name => {
  map[name] = true;
});
var uniqueNames = Object.keys(map);

console.log(uniqueNames);

You could do the same thing in one expression if you like:

var uniqueNames = Object.keys(allNames.reduce((m, n) => (m[n] = true, m), {}));

but I find the imperative form easier to read.

joeytwiddle
  • 29,306
  • 13
  • 121
  • 110
  • I used ES6 arrow functions for clarity. If you really are targeting ES5 without a transpiler, then you will need to use the full form instead: `function (item) { return item.name; }` – joeytwiddle Feb 28 '18 at 08:12
  • As an alternative to `Object.keys()`, [this answer](https://stackoverflow.com/a/9229821/99777) uses `filter()` to keep only those names which have not yet been stored in the map, which is quite elegant. – joeytwiddle Feb 28 '18 at 09:19
0

Use array#forEach() and array#indexOf() methods like this if you want maximum compatibility yet, concise syntax:

const array = [{ id: 123, value: "value1", name:"Name1" }, { id: 124, value: "value2", name: "Name1" }, { id: 125, value: "value3", name: "Name2" }, { id: 126, value: "value4", name: "Name2" }]

// initialize an empty array named names
let names = [];

// iterate through every element of `array` & check if it's 'name' key's value already in names array if not ADD it 
array.forEach(function(element) { if (names.indexOf(element.name) === -1) names.push(element.name) });
// or use tilde like this:
//array.forEach(function(element) { if (~names.indexOf(element.name)) names.push(element.name) });

console.log(names);

However, if compatibility is not an issue use ECMAScript 6's Set object, array#map and Array.from() methods like this:

const array = [{ id: 123, value: "value1", name:"Name1" }, { id: 124, value: "value2", name: "Name1" }, { id: 125, value: "value3", name: "Name2" }, { id: 126, value: "value4", name: "Name2" }];

// iterate through every element from array using map and store it in Set(a Set won't have duplicates) then convert the Set back to Array(using Array.from)
let names = Array.from(new Set(array.map(element => element.name)));

console.log(names);
BlackBeard
  • 10,246
  • 7
  • 52
  • 62
0

I see there is a lot of spread-Set-like solutions, that aren't optimal.

This solution is simpler, more efficient and doesn't needs to recreate array:

const array = [{ id: 123, value: "value1", name:"Name1" }, { id: 124, value: "value2", name: "Name1" }, { id: 125, value: "value3", name: "Name2" }, { id: 126, value: "value4", name: "Name2" }]

const res = array.map(e => e.name)
                 .filter((e, i, a) => a.indexOf(e) == i)

console.log(res)
ulou
  • 5,542
  • 5
  • 37
  • 47
-1

For those seeking a 1 liner

const names = array.reduce((acc, {name}) => acc.includes(name) ? acc : [name, ...acc], []);

or without using methods on the array's prototype

const { reduce, includes } = Array;
const names = reduce(array, (acc, {name}) => includes(acc, name) ? acc : [name, ...acc], []);

could be usefull for writing some pure functions for dealing with this

const get_uniq_values = (key, arr) => reduce(arr, (a, o) => includes(a, o[key]) ? a : [o[key], ...a], []);
Tyrell
  • 1
  • 1
-1
var __array=[{id:123, value:"value1", name:"Name1"},{id:124, value:"value2", name:"Name1"},{id:125, value:"value3", name:"Name2"},{id:126, value:"value4", name:"Name2"}];

function __checkArray(__obj){
    var flag = true;
    for(let i=0; i < __array.length; i++){
        if(__obj.id == __array.id){
            flag = false;
            break;
        }
    }

    return flag;
}

var __valToPush = {id: 127, value: "value5", name: "Name3"};
if(__checkArray(__valToPush)){
    __array.push(__valToPush)
}
AKHIL
  • 135
  • 3