How to get distinct values from an array of objects in JavaScript?
Asked Answered
M

64

862

Assuming I have the following:

var array = 
    [
        {"name":"Joe", "age":17}, 
        {"name":"Bob", "age":17}, 
        {"name":"Carl", "age": 35}
    ]

What is the best way to be able to get an array of all of the distinct ages such that I get an result array of:

[17, 35]

Is there some way I could alternatively structure the data or better method such that I would not have to iterate through each array checking the value of "age" and check against another array for its existence, and add it if not?

If there was some way I could just pull out the distinct ages without iterating...

Current inefficient way I would like to improve... If it means that instead of "array" being an array of objects, but a "map" of objects with some unique key (i.e. "1,2,3") that would be okay too. I'm just looking for the most performance efficient way.

The following is how I currently do it, but for me, iteration appears to just be crummy for efficiency even though it does work...

var distinct = []
for (var i = 0; i < array.length; i++)
   if (array[i].age not in distinct)
      distinct.push(array[i].age)
Magalymagan answered 28/2, 2013 at 1:33 Comment(6)
iteration isn't "crummy for efficiency" and you can't do anything to every element "without iterating". you can use various functional-looking methods, but ultimately, something on some level has to iterate over the items.Chaechaeronea
//100% running code const listOfTags = [{ id: 1, label: "Hello", color: "red", sorting: 0 }, { id: 2, label: "World", color: "green", sorting: 1 }, { id: 3, label: "Hello", color: "blue", sorting: 4 }, { id: 4, label: "Sunshine", color: "yellow", sorting: 5 }, { id: 5, label: "Hello", color: "red", sorting: 6 }], keys = ['label', 'color'], filtered = listOfTags.filter( (s => o => (k => !s.has(k) && s.add(k)) (keys.map(k => o[k]).join('|')) ) (new Set) ); console.log(filtered);Depersonalization
the bounty is great, but the question with the given data and answer is already answered here: #53543382. what is the purpose of the bounty? should i answer this particular problem with two or more keys?Hogg
Set object and maps are wasteful. This job just takes a simple .reduce() stage.Distiller
Please check this example, https://mcmap.net/q/37119/-get-the-distinct-object-from-the-array-of-objects-in-typescript-angular-8 .Albuminoid
/*Answer:*/ var ListDistinct=[]; array.forEach((m)=>{ if(ListDistinct.indexOf(m.age)<0)ListDistinct.push(m.age); }); console.log(ListDistinct);Sherl
C
158

If this were PHP I'd build an array with the keys and take array_keys at the end, but JS has no such luxury. Instead, try this:

var flags = [], output = [], l = array.length, i;
for( i=0; i<l; i++) {
    if( flags[array[i].age]) continue;
    flags[array[i].age] = true;
    output.push(array[i].age);
}
Cryptogram answered 28/2, 2013 at 1:36 Comment(6)
No, because array_unique would compare the entire item, not just the age as is asked here.Cryptogram
I think flags = {} is more better than flags = []Eleanoreleanora
@Eleanoreleanora perhaps, although there's nothing really wrong with sparse arrays - and I also assumed that age is a relatively small integer (<120 surely)Cryptogram
how we could also print out the total number of people that have same age?Weep
@NiettheDarkAbsol how would you go about something like this? jsfiddle.net/BeerusDev/asgq8n7e/28 I have an array with objects, and each key Days has an array.Depart
how can i group the elements with a header with the distinct values like 17: a,b; 19:b,c,d?Millhon
S
1298

If you are using ES6/ES2015 or later you can do it this way:

const data = [
  { group: 'A', name: 'SD' }, 
  { group: 'B', name: 'FI' }, 
  { group: 'A', name: 'MM' },
  { group: 'B', name: 'CO'}
];
const unique = [...new Set(data.map(item => item.group))]; // [ 'A', 'B']

Here is an example on how to do it.

Supplication answered 29/1, 2016 at 19:31 Comment(7)
I get an error : TypeError: (intermediate value).slice is not a functionPinpoint
Thank you, but what means the ... ?Promenade
@Promenade ... means spread operator. In this case it means create new set and spread it in a list. You can find more about it here: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Supplication
for typescript you have to use new Set wrapped in Array.from()... i will provide that answer below. @AngJobsWarms
While your answer is correct per se it misses key use case - where you'd want to keep other object properties rather than just values. It took me quite a bit of searching, but lodash uniqBy function should achieve that and here's a link of a vanilla implementation: #40801849Laster
@salah9 ie11 does not support new Set developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Chelsea
This solution was give almost verbatim a few months earlier by @Russell Vea. Did you check for existing answers? But then again, 266+ votes show that nobody else checked either.Selwin
C
559

For those who want to return object with all properties unique by key

const array =
  [
    { "name": "Joe", "age": 17 },
    { "name": "Bob", "age": 17 },
    { "name": "Carl", "age": 35 }
  ]

const key = 'age';

const arrayUniqueByKey = [...new Map(array.map(item =>
  [item[key], item])).values()];

console.log(arrayUniqueByKey);

   /*OUTPUT
       [
        { "name": "Bob", "age": 17 },
        { "name": "Carl", "age": 35 }
       ]
   */

 // Note: this will pick the last duplicated item in the list.
Chamber answered 17/10, 2019 at 9:52 Comment(5)
One improvement -- add .filter(Boolean) before the map call. If any of your objects in the array are null then this will throw Cannot read properties of null (reading 'id')Forrest
This answer really taught me things. Thank you! @ArunSainiAshantiashbaugh
I would expect other way around, keep 1st remove other, those are duplicates...Rogovy
@ToKra You can reverse the array input "array.reverse()" and then reverse array output "arrayUniqueByKey.reverse()"Chamber
how would it look like it only retrieves where age should be more than 0?Connate
C
327

using ES6

let array = [
  { "name": "Joe", "age": 17 },
  { "name": "Bob", "age": 17 },
  { "name": "Carl", "age": 35 }
];
array.map(item => item.age)
  .filter((value, index, self) => self.indexOf(value) === index)

> [17, 35]
Congener answered 10/12, 2015 at 10:23 Comment(8)
If I want to get the value with it's index then how can I get Example: I want to get {"name": "Bob", "age":"17"},{"name": "Bob", "age":"17"}Garthgartner
That's why you have to keep scrollingMckinnon
@AbdullahAlMamun you can use map in .filter instead of call .map first, like this: array.filter((value, index, self) => self.map(x => x.age).indexOf(value.age) == index)Punch
ps: .map inside .filter can be slow, so be careful with large arraysPunch
@IvanNosov your snippet is very good although several lines of explanation how it works with pointers to map and filter documentation will make it perfect and clear for beginnersElouise
@Drenai but indexOf is. So, it's the same thing - it comes down to O(n^2) complexity.Betthezel
this very inneficient, using a Set is the way to goEntropy
Also, I tested more than 48000+ array of objects for getting unique results. This answer gave the fastest result. Thanks, man.Peony
E
189

Using ES6 features, you could do something like:

const uniqueAges = [...new Set( array.map(obj => obj.age)) ];
Embry answered 4/11, 2015 at 1:37 Comment(4)
This approach will only work on primitive types (which is the case here as ages are an array of numbers) yet will fail for objects.Lambard
any idea how to make it work for objects with two keys?Cristophercristy
Something like const uniqueObjects = [ ...new Set( array.map( obj => obj.age) ) ].map( age=> { return array.find(obj => obj.age === age) } )Nancee
set may change order of elementsNag
W
183

This is how you would solve this using new Set via ES6 as of August 25th, 2017

TypeScript

 Array.from(new Set(yourArray.map((item: any) => item.id)))

JS

 Array.from(new Set(yourArray.map((item) => item.id)))
Warms answered 25/8, 2017 at 16:50 Comment(6)
this helped, was getting before thanks. Type 'Set' is not an array typePapilionaceous
This is a great answer. Untill i scrolled down to this one, i was going to write an answer saying: Object.keys(values.reduce<any>((x, y) => {x[y] = null; return x; }, {})); But this answer is more concise and applies to all data types instead of just strings.Earleenearlene
Non typescript version: Array.from(new Set(yourArray.map((item) => item.id)))Piccaninny
Should remove the typescript part because it is not a part of the language and is also a (temporary) nicheIndelicacy
The is no id in the OP code, and also, this example seems pasted from your specific use-case (at that time) and should be adjust to include all possible array items, such as primitives, objects and so on.Indelicacy
hi @Indelicacy feel free to edit or put your changes here and I will edit. Good point about Typescript.Warms
C
158

If this were PHP I'd build an array with the keys and take array_keys at the end, but JS has no such luxury. Instead, try this:

var flags = [], output = [], l = array.length, i;
for( i=0; i<l; i++) {
    if( flags[array[i].age]) continue;
    flags[array[i].age] = true;
    output.push(array[i].age);
}
Cryptogram answered 28/2, 2013 at 1:36 Comment(6)
No, because array_unique would compare the entire item, not just the age as is asked here.Cryptogram
I think flags = {} is more better than flags = []Eleanoreleanora
@Eleanoreleanora perhaps, although there's nothing really wrong with sparse arrays - and I also assumed that age is a relatively small integer (<120 surely)Cryptogram
how we could also print out the total number of people that have same age?Weep
@NiettheDarkAbsol how would you go about something like this? jsfiddle.net/BeerusDev/asgq8n7e/28 I have an array with objects, and each key Days has an array.Depart
how can i group the elements with a header with the distinct values like 17: a,b; 19:b,c,d?Millhon
A
158

You could use a dictionary approach like this one. Basically you assign the value you want to be distinct as a key in the "dictionary" (here we use an array as an object to avoid dictionary-mode). If the key did not exist then you add that value as distinct.

Here is a working demo:

var array = [{"name":"Joe", "age":17}, {"name":"Bob", "age":17}, {"name":"Carl", "age": 35}];
var unique = [];
var distinct = [];
for( let i = 0; i < array.length; i++ ){
  if( !unique[array[i].age]){
    distinct.push(array[i].age);
    unique[array[i].age] = 1;
  }
}
var d = document.getElementById("d");
d.innerHTML = "" + distinct;
<div id="d"></div>

This will be O(n) where n is the number of objects in array and m is the number of unique values. There is no faster way than O(n) because you must inspect each value at least once.

The previous version of this used an object, and for in. These were minor in nature, and have since been minorly updated above. However, the reason for a seeming advance in performance between the two versions in the original jsperf was due to the data sample size being so small. Thus, the main comparison in the previous version was looking at the difference between the internal map and filter use versus the dictionary mode lookups.

I have updated the code above, as noted, however, I have also updated the jsperf to look through 1000 objects instead of 3. 3 overlooked many of the performance pitfalls involved (obsolete jsperf).

Performance

https://jsperf.com/filter-vs-dictionary-more-data When I ran this dictionary was 96% faster.

filter vs dictionary

Authoritative answered 28/2, 2013 at 1:37 Comment(18)
fast, and without any additional plugins required. Really coolIntaglio
how about if( typeof(unique[array[i].age]) == "undefined"){ distinct.push(array[i].age); unique[array[i].age] = 0; }Harlandharle
Wow the performance has really changed in favour of the map option. Click that jsperf link!Swagerty
No, it hasn't. Map ran 45% slower, coming in at 783k opts per sec versus dictionary's 1.435M opts per second.Authoritative
Sorry, newbie here. Why did you use an object for unique and not an array? what did it make for a difference? why did normal object work?Teilo
@Teilo - The object was used to facilitate the "dictionary approach". Inside the object, each occurrence being tracked is added as a key (or a bin). This way, when we come across an occurrence, we can check to see if the key (or bin) exists; if it does exist, we know that the occurrence is not unique - if it does not exist, we know that the occurrence is unique and add it in.Authoritative
@Teilo - The reason for using an object is that the lookup is O(1) since it checks for a property name once, whereas an array is O(n) since it has to look at every value to check for existence. At the end of the process, the set of keys (bins) in the object represents the set of unique occurrences.Authoritative
Thanks, so using the object is basically like a using a key-value-store / hashset. if I change the code to var unique = []; var distinct = []; then I will init both as arrays but it will be slower.Teilo
You mean here distinct.push(array[i]); not distinct.push(array[i].age); , right?Investigate
@Accountantم - No. The goal was to get the distinct ages. It is correct as written.Authoritative
@TravisJ Oh yes, sorry I thought he needs to get distinct objects based on ageInvestigate
Besides performance it allows me to store something using unique[array[i].age] = 0; Another way is to use Set(). Curious which one is faster.Zetes
@Harlandharle does unique[array[i].age] = 0; help performance? else what's the purpose?Zetes
Strange. Currently it's 86% and 96% slower than filter in chrome and firefox respectively if you check here jsperf.com/filter-versus-dictionaryObadias
@AbdulRauf - I can reproduce that as well. Perhaps it is a result of changes in EcmaScript. If I have some time to dig in and look around I will try to edit that in.Authoritative
It seems an array for unique is faster to date: jsperf.com/filter-versus-dictionary/57 (map is still the fastest)Autumn
@AbdulRauf and ToonVanDooren - Please see my edit.Authoritative
Thanks a lot! Especially for confirming the performance gain!Conias
E
73

I'd just map and remove dups:

var ages = array.map(function(obj) { return obj.age; });
ages = ages.filter(function(v,i) { return ages.indexOf(v) == i; });

console.log(ages); //=> [17, 35]

Edit: Aight! Not the most efficient way in terms of performance, but the simplest most readable IMO. If you really care about micro-optimization or you have huge amounts of data then a regular for loop is going to be more "efficient".

Elma answered 28/2, 2013 at 1:40 Comment(5)
@Elma - "the most performance efficient way" - This approach is slow.Authoritative
@Eevee: I see. The underscore version in your answer is not very fast either, I mean, in the end you choose what's more convenient, I doubt 1-30% more or less reflects "huge improvement" in generic tests and when OP/s are in the thousands.Elma
well, sure. with only three elements, the fastest thing is to make three variables and use some ifs. you'll get some very different results with three million.Chaechaeronea
not fast but in my case it did the trick as I'm not dealing with a large dataset, thanks.Autochthonous
Isn't this O(N^2)? Not recommended. You want to use something constant time for inner-lookups, like a map.Ophir
F
58
var unique = array
    .map(p => p.age)
    .filter((age, index, arr) => arr.indexOf(age) == index)
    .sort(); // sorting is optional

// or in ES6

var unique = [...new Set(array.map(p => p.age))];

// or with lodash

var unique = _.uniq(_.map(array, 'age'));

ES6 example

const data = [
  { name: "Joe", age: 17}, 
  { name: "Bob", age: 17}, 
  { name: "Carl", age: 35}
];

const arr = data.map(p => p.age); // [17, 17, 35]
const s = new Set(arr); // {17, 35} a set removes duplications, but it's still a set
const unique = [...s]; // [17, 35] Use the spread operator to transform a set into an Array
// or use Array.from to transform a set into an array
const unique2 = Array.from(s); // [17, 35]
Folkestone answered 16/12, 2018 at 16:18 Comment(3)
While this code may answer the question, providing additional context regarding how and why it solves the problem would improve the answer's long-term value.Stress
The first one which is using the indexOf vs the index is just brilliant.Geum
Note that this only works for primitive values. If you have an array of dates, then you need some more customized methods. Read more here: codeburst.io/javascript-array-distinct-5edc93501dc4Babar
G
55

This is a slight variation on the ES6 version if you need the entire object:

let arr = [
    {"name":"Joe", "age":17}, 
    {"name":"Bob", "age":17}, 
    {"name":"Carl", "age": 35}
]
arr.filter((a, i) => arr.findIndex((s) => a.age === s.age) === i) // [{"name":"Joe", "age":17}, {"name":"Carl", "age": 35}]
Ghostly answered 29/4, 2021 at 17:11 Comment(2)
I executed this and it's not filtering as described and the result is // [{"name":"Joe", "age":17}]Nalda
Correctly updated the filter param to be age. Thanks for spottingGhostly
A
37

There are many valid answers already, but I wanted to add one that uses only the reduce() method because it is clean and simple.

function uniqueBy(arr, prop){
  return arr.reduce((a, d) => {
    if (!a.includes(d[prop])) { a.push(d[prop]); }
    return a;
  }, []);
}

Use it like this:

var array = [
  {"name": "Joe", "age": 17}, 
  {"name": "Bob", "age": 17}, 
  {"name": "Carl", "age": 35}
];

var ages = uniqueBy(array, "age");
console.log(ages); // [17, 35]
Aftersensation answered 2/8, 2018 at 13:39 Comment(1)
is this slower than the top answer ?Killingsworth
R
36

const array = [
  {"id":"93","name":"CVAM_NGP_KW"},
  {"id":"94","name":"CVAM_NGP_PB"},
  {"id":"93","name":"CVAM_NGP_KW"},
  {"id":"94","name":"CVAM_NGP_PB"}
]

function uniq(array, field) {
  return array.reduce((accumulator, current) => {
      if(!accumulator.includes(current[field])) {
        accumulator.push(current[field])
      }
      return accumulator;
    }, []
  )
}

const ids = uniq(array, 'id');
console.log(ids)

/* output 
["93", "94"]
*/
Refugiorefulgence answered 12/8, 2019 at 22:8 Comment(1)
this looks like O(n^2)Cristophercristy
L
26

I have a small solution

let data = [{id: 1}, {id: 2}, {id: 3}, {id: 2}, {id: 3}];

let result = data.filter((value, index, self) => self.findIndex((m) => m.id === value.id) === index);
Luddite answered 21/1, 2021 at 14:11 Comment(0)
B
23

The forEach version of @travis-j's answer (helpful on modern browsers and Node JS world):

var unique = {};
var distinct = [];
array.forEach(function (x) {
  if (!unique[x.age]) {
    distinct.push(x.age);
    unique[x.age] = true;
  }
});

34% faster on Chrome v29.0.1547: http://jsperf.com/filter-versus-dictionary/3

And a generic solution that takes a mapper function (tad slower than direct map, but that's expected):

function uniqueBy(arr, fn) {
  var unique = {};
  var distinct = [];
  arr.forEach(function (x) {
    var key = fn(x);
    if (!unique[key]) {
      distinct.push(key);
      unique[key] = true;
    }
  });
  return distinct;
}

// usage
uniqueBy(array, function(x){return x.age;}); // outputs [17, 35]
Bronny answered 20/9, 2013 at 18:28 Comment(2)
I like the generic solution best since it's unlikely that age is the only distinct value needed in a real world scenario.Bertram
In the generic, change "distinct.push(key)" to "distinct.push(x)" to return a list of the actual elements, which I find highly usable!Goldsberry
C
17

I've started sticking Underscore in all new projects by default just so I never have to think about these little data-munging problems.

var array = [{"name":"Joe", "age":17}, {"name":"Bob", "age":17}, {"name":"Carl", "age": 35}];
console.log(_.chain(array).map(function(item) { return item.age }).uniq().value());

Produces [17, 35].

Chaechaeronea answered 28/2, 2013 at 1:45 Comment(0)
R
14

Here's another way to solve this:

var result = {};
for(var i in array) {
    result[array[i].age] = null;
}

result = Object.keys(result);

or

result = Object.values(result);

I have no idea how fast this solution is compared to the others, but I like the cleaner look. ;-)


EDIT: Okay, the above seems to be the slowest solution of all here.

I've created a performance test case here: http://jsperf.com/distinct-values-from-array

Instead of testing for the ages (Integers), I chose to compare the names (Strings).

Method 1 (TS's solution) is very fast. Interestingly enough, Method 7 outperforms all other solutions, here I just got rid of .indexOf() and used a "manual" implementation of it, avoiding looped function calling:

var result = [];
loop1: for (var i = 0; i < array.length; i++) {
    var name = array[i].name;
    for (var i2 = 0; i2 < result.length; i2++) {
        if (result[i2] == name) {
            continue loop1;
        }
    }
    result.push(name);
}

The difference in performance using Safari & Firefox is amazing, and it seems like Chrome does the best job on optimization.

I'm not exactly sure why the above snippets is so fast compared to the others, maybe someone wiser than me has an answer. ;-)

Remount answered 13/2, 2015 at 17:47 Comment(0)
C
14

const array =
  [
    { "name": "Joe", "age": 17 },
    { "name": "Bob", "age": 17 },
    { "name": "Carl", "age": 35 }
  ]

const key = 'age';

const arrayUniqueByKey = [...new Map(array.map(item =>
  [item[key], item])).values()];

console.log(arrayUniqueByKey);
Caffrey answered 16/10, 2021 at 22:30 Comment(2)
This is a boom solution.Korney
Nice, :) seems like you copied my answer: #15126420Chamber
C
11

using lodash

var array = [
    { "name": "Joe", "age": 17 },
    { "name": "Bob", "age": 17 },
    { "name": "Carl", "age": 35 }
];
_.chain(array).pluck('age').unique().value();
> [17, 35]
Congener answered 21/1, 2015 at 14:1 Comment(5)
Can you explain how to do this with plain javascript as is implied in the question?Puffball
it is an underscore function _.chainGabrielagabriele
but pluck() is not in lodash.Disuse
_.chain(array).map('age').unique().value(); worked for me.Disuse
version 4.0 had some breaking changes refer - https://mcmap.net/q/37121/-lodash-remove-duplicates-from-arrayBitter
T
11

var array = [
          {"name":"Joe", "age":17}, 
          {"name":"Bob", "age":17}, 
          {"name":"Carl", "age": 35}
      ];

      const ages = [...new Set(array.reduce((a, c) => [...a, c.age], []))];
    
      console.log(ages);
Tanbark answered 12/4, 2021 at 1:57 Comment(3)
Nice way of using reduce and set. ThanksSublunar
awesome, how can I get count of each of item?Sissy
Thanks for the comment, Maybe I didn't get fully your requirement, I assume that you are looking at the below one - https://mcmap.net/q/37122/-counting-the-occurrences-frequency-of-array-elementsTanbark
C
9

Simple distinct filter using Maps :

let array = 
    [
        {"name":"Joe", "age":17}, 
        {"name":"Bob", "age":17}, 
        {"name":"Carl", "age": 35}
    ];

let data = new Map();

for (let obj of array) {
  data.set(obj.age, obj);
}

let out = [...data.values()];

console.log(out);
Charmain answered 5/8, 2020 at 23:5 Comment(0)
S
6
function get_unique_values_from_array_object(array,property){
    var unique = {};
    var distinct = [];
    for( var i in array ){
       if( typeof(unique[array[i][property]]) == "undefined"){
          distinct.push(array[i]);
       }
       unique[array[i][property]] = 0;
    }
    return distinct;
}
Scurvy answered 30/12, 2015 at 13:35 Comment(0)
D
6

Using Lodash

var array = [
    { "name": "Joe", "age": 17 },
    { "name": "Bob", "age": 17 },
    { "name": "Carl", "age": 35 }
];

_.chain(array).map('age').unique().value();

Returns [17,35]

Disuse answered 29/11, 2016 at 11:10 Comment(0)
A
6

If you want to have an unique list of objects returned back. here is another alternative:

const unique = (arr, encoder=JSON.stringify, decoder=JSON.parse) =>
  [...new Set(arr.map(item => encoder(item)))].map(item => decoder(item));

Which will turn this:

unique([{"name": "john"}, {"name": "sarah"}, {"name": "john"}])

into

[{"name": "john"}, {"name": "sarah"}]

The trick here is that we are first encoding the items into strings using JSON.stringify, and then we are converting that to a Set (which makes the list of strings unique) and then we are converting it back to the original objects using JSON.parse.

Alas answered 7/7, 2020 at 13:54 Comment(0)
H
6

If you wanna iterate through unique items use this:
(more flexible version of https://mcmap.net/q/36981/-how-to-get-distinct-values-from-an-array-of-objects-in-javascript)

const array = [
  {"name":"Joe", "age":17}, 
  {"name":"Bob", "age":17}, 
  {"name":"Carl", "age": 35},
];

const uniqBy = (arr, selector = (item) => item) => {
    const map = new Map();
    arr.forEach((item) => {
        const prop = selector(item);
        if (!map.has(prop)) map.set(prop, item);
    });
    return [...map.values()];
}

const uniqItems = uniqBy(array, (item) => item.age);

console.log('uniqItems: ', uniqItems);

If you just need unique values use this:
(duplicate of https://mcmap.net/q/36981/-how-to-get-distinct-values-from-an-array-of-objects-in-javascript, just for completeness)

const array = [
  {"name":"Joe", "age":17}, 
  {"name":"Bob", "age":17}, 
  {"name":"Carl", "age": 35},
];

const uniq = (items) => [...new Set(items)];

const uniqAges = uniq(array.map((item) => item.age));

console.log('uniqAges: ', uniqAges);
Highfalutin answered 17/8, 2022 at 11:48 Comment(0)
M
5

Here's a versatile solution that uses reduce, allows for mapping, and maintains insertion order.

items: An array

mapper: A unary function that maps the item to the criteria, or empty to map the item itself.

function distinct(items, mapper) {
    if (!mapper) mapper = (item)=>item;
    return items.map(mapper).reduce((acc, item) => {
        if (acc.indexOf(item) === -1) acc.push(item);
        return acc;
    }, []);
}

Usage

const distinctLastNames = distinct(items, (item)=>item.lastName);
const distinctItems = distinct(items);

You can add this to your Array prototype and leave out the items parameter if that's your style...

const distinctLastNames = items.distinct( (item)=>item.lastName) ) ;
const distinctItems = items.distinct() ;

You can also use a Set instead of an Array to speed up the matching.

function distinct(items, mapper) {
    if (!mapper) mapper = (item)=>item;
    return items.map(mapper).reduce((acc, item) => {
        acc.add(item);
        return acc;
    }, new Set());
}
Mckale answered 18/1, 2017 at 13:52 Comment(0)
T
4

Just found this and I thought it's useful

_.map(_.indexBy(records, '_id'), function(obj){return obj})

Again using underscore, so if you have an object like this

var records = [{_id:1,name:'one', _id:2,name:'two', _id:1,name:'one'}]

it will give you the unique objects only.

What happens here is that indexBy returns a map like this

{ 1:{_id:1,name:'one'}, 2:{_id:2,name:'two'} }

and just because it's a map, all keys are unique.

Then I'm just mapping this list back to array.

In case you need only the distinct values

_.map(_.indexBy(records, '_id'), function(obj,key){return key})

Keep in mind that the key is returned as a string so, if you need integers instead, you should do

_.map(_.indexBy(records, '_id'), function(obj,key){return parseInt(key)})
Theatrical answered 23/7, 2014 at 12:10 Comment(0)
S
4

underscore.js _.uniq(_.pluck(array,"age"))

Squirm answered 2/5, 2016 at 17:24 Comment(2)
Please add an explanation of how this answers the question.Confiture
_.pluck has been removed in favor of _.mapGilges
D
4

i think you are looking for groupBy function (using Lodash)

_personsList = [{"name":"Joe", "age":17}, 
                {"name":"Bob", "age":17}, 
                {"name":"Carl", "age": 35}];
_uniqAgeList = _.groupBy(_personsList,"age");
_uniqAges = Object.keys(_uniqAgeList);

produces result:

17,35

jsFiddle demo:http://jsfiddle.net/4J2SX/201/

Dombrowski answered 10/5, 2017 at 12:16 Comment(0)
R
4
[...new Set([
    { "name": "Joe", "age": 17 },
    { "name": "Bob", "age": 17 },
    { "name": "Carl", "age": 35 }
  ].map(({ age }) => age))]
Rejoice answered 24/10, 2019 at 8:0 Comment(0)
D
4

Primitive Types

var unique = [...new Set(array.map(item => item.pritiveAttribute))];

For complex types e.g. Objects

var unique = [...new DeepSet(array.map(item => item.Object))];

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)
  }
}

Dill answered 20/4, 2021 at 11:6 Comment(0)
B
4

    var array = 
    [
        {"name":"Joe", "age":17}, 
        {"name":"Bob", "age":17}, 
        {"name":"Carl", "age": 35}
    ]

    console.log(Object.keys(array.reduce((r,{age}) => (r[age]='', r) , {})))

Output:

Array ["17", "35"]
Byword answered 16/7, 2021 at 18:5 Comment(0)
H
3

If you have Array.prototype.includes or are willing to polyfill it, this works:

var ages = []; array.forEach(function(x) { if (!ages.includes(x.age)) ages.push(x.age); });
Hesler answered 20/5, 2015 at 23:13 Comment(0)
S
3

If like me you prefer a more "functional" without compromising speed, this example uses fast dictionary lookup wrapped inside reduce closure.

var array = 
[
    {"name":"Joe", "age":17}, 
    {"name":"Bob", "age":17}, 
    {"name":"Carl", "age": 35}
]
var uniqueAges = array.reduce((p,c,i,a) => {
    if(!p[0][c.age]) {
        p[1].push(p[0][c.age] = c.age);
    }
    if(i<a.length-1) {
        return p
    } else {
        return p[1]
    }
}, [{},[]])

According to this test my solution is twice as fast as the proposed answer

Surfacetosurface answered 31/1, 2016 at 6:54 Comment(0)
C
3

Simple one-liner with great performance. 6% faster than the ES6 solutions in my tests.

var ages = array.map(function(o){return o.age}).filter(function(v,i,a) {
    return a.indexOf(v)===i
});
Casta answered 30/4, 2019 at 7:21 Comment(2)
@Zetes Care to add a multi-line that is easy to read? Looking at the others here I really don't feel they are easy to read or understand. I think it's best to place this in a function that describes what it does.Casta
With arrow functions: array.map( o => o.age).filter( (v,i,a) => a.indexOf(v)===i). I use the function keyword so rarely now that I have to read things twice when I see it 😊Uniat
U
3

I know my code is little length and little time complexity but it's understandable so I tried this way.

I'm trying to develop prototype based function here and code also change.

Here,Distinct is my own prototype function.

<script>
  var array = [{
      "name": "Joe",
      "age": 17
    },
    {
      "name": "Bob",
      "age": 17
    },
    {
      "name": "Carl",
      "age": 35
    }
  ]

  Array.prototype.Distinct = () => {
    var output = [];
    for (let i = 0; i < array.length; i++) {
      let flag = true;
      for (let j = 0; j < output.length; j++) {
        if (array[i].age == output[j]) {
          flag = false;
          break;
        }
      }
      if (flag)
        output.push(array[i].age);
    }
    return output;
  }
  //Distinct is my own function
  console.log(array.Distinct());
</script>
Underbid answered 12/11, 2019 at 14:23 Comment(0)
A
3

const array = [{
    "name": "Joe",
    "age": 17
  },
  {
    "name": "Bob",
    "age": 17
  },
  {
    "name": "Carl",
    "age": 35
  }
]

const uniqueArrayByProperty = (array, callback) => {
  return array.reduce((prev, item) => {
    const v = callback(item);    
    if (!prev.includes(v)) prev.push(v)          
    return prev
  }, [])    
}

console.log(uniqueArrayByProperty(array, it => it.age));
Actinism answered 17/5, 2021 at 14:9 Comment(0)
R
3

There are several options of course, but reduce() with Map() option seems to be performant as well:

const array = [
  { "name": "Joe", "age": 17 },
  { "name": "Bob", "age": 17 },
  { "name": "Carl", "age": 35 }
]

const key = 'age'

const redUniq = [
  ...array
    .reduce((uniq, curr) => {
      if (!uniq.has(curr[key])) {
        uniq.set(curr[key], curr);
      }
      return uniq;
    }, new Map())
    .values()
];

console.log(redUniq); //Output: [ { name: 'Joe', age: 17 }, { name: 'Carl', age: 35 } ]

see comparison with times/perf: https://replit.com/@tokra1/Comparison-of-Deduplicate-array-of-objects-by-specific-key

Rogovy answered 8/8, 2023 at 16:12 Comment(0)
C
2

My below code will show the unique array of ages as well as new array not having duplicate age

var data = [
  {"name": "Joe", "age": 17}, 
  {"name": "Bob", "age": 17}, 
  {"name": "Carl", "age": 35}
];

var unique = [];
var tempArr = [];
data.forEach((value, index) => {
    if (unique.indexOf(value.age) === -1) {
        unique.push(value.age);
    } else {
        tempArr.push(index);    
    }
});
tempArr.reverse();
tempArr.forEach(ele => {
    data.splice(ele, 1);
});
console.log('Unique Ages', unique);
console.log('Unique Array', data);```
Canner answered 10/5, 2019 at 10:30 Comment(0)
C
2

There are a lot of great answers here, but none of them have addressed the following line:

Is there some way I could alternatively structure the data

I would create an object whose keys are the ages, each pointing to an array of names.

var array = [{ "name": "Joe", "age": 17 }, { "name": "Bob", "age": 17 }, { "name": "Carl", "age": 35 }];

var map = array.reduce(function(result, item) {
  result[item.age] = result[item.age] || [];
  result[item.age].push(item.name);
  return result;
}, {});

console.log(Object.keys(map));
console.log(map);

This way you've converted the data structure into one that is very easy to retrieve the distinct ages from.

Here is a more compact version that also stores the entire object instead of just the name (in case you are dealing with objects with more than 2 properties so they cant be stored as key and value).

var array = [{ "name": "Joe", "age": 17 }, { "name": "Bob", "age": 17 }, { "name": "Carl", "age": 35 }];

var map = array.reduce((r, i) => ((r[i.age] = r[i.age] || []).push(i), r), {});

console.log(Object.keys(map));
console.log(map);
Contaminate answered 12/11, 2019 at 8:13 Comment(0)
B
1

I wrote my own in TypeScript, for a generic case, like that in Kotlin's Array.distinctBy {}...

function distinctBy<T, U extends string | number>(array: T[], mapFn: (el: T) => U) {
  const uniqueKeys = new Set(array.map(mapFn));
  return array.filter((el) => uniqueKeys.has(mapFn(el)));
}

Where U is hashable, of course. For Objects, you might need https://www.npmjs.com/package/es6-json-stable-stringify

Borman answered 27/8, 2019 at 10:40 Comment(1)
Does this actually work though? Your array filter checks if the key of the element is in the set of unique keys, won't this always be true, even for duplicates?Jampan
C
1

In case you need unique of whole object

const _ = require('lodash');

var objects = [
  { 'x': 1, 'y': 2 },
  { 'y': 1, 'x': 2 },
  { 'x': 2, 'y': 1 },
  { 'x': 1, 'y': 2 }
];

_.uniqWith(objects, _.isEqual);

[Object {x: 1, y: 2}, Object {x: 2, y: 1}]

Cristophercristy answered 10/10, 2019 at 10:34 Comment(0)
G
1

Answering this old question is pretty pointless, but there is a simple answer that speaks to the nature of Javascript. Objects in Javascript are inherently hash tables. We can use this to get a hash of unique keys:

var o = {}; array.map(function(v){ o[v.age] = 1; });

Then we can reduce the hash to an array of unique values:

var a2 = []; for (k in o){ a2.push(k); }

That is all you need. The array a2 contains just the unique ages.

Gramarye answered 14/11, 2019 at 2:54 Comment(0)
R
1

const array = [
  { "name": "Joe", "age": 17 }, 
  { "name":"Bob", "age":17 },
  { "name":"Carl", "age": 35 }
]

const allAges = array.map(a => a.age);

const uniqueSet = new Set(allAges)
const uniqueArray = [...uniqueSet]

console.log(uniqueArray)
Refugiorefulgence answered 9/1, 2020 at 21:40 Comment(0)
I
1

There is linq.js - LINQ for JavaScript package (npm install linq), that should be familiar for .Net developers.

Among others methods shown in samples there are distinct overloads.

An example to distinct objects by property value from an array of objects is

Enumerable.from(array).distinct(“$.id”).toArray();

From https://medium.com/@xmedeko/i-recommend-you-to-try-https-github-com-mihaifm-linq-20a4e3c090e9

Inn answered 9/5, 2020 at 5:55 Comment(0)
L
1

I picked up random samples and tested it against the 100,000 items as below:

let array=[]
for (var i=1;i<100000;i++){

 let j= Math.floor(Math.random() * i) + 1
  array.push({"name":"Joe"+j, "age":j})
}

And here the performance result for each:

  Vlad Bezden Time:         === > 15ms
  Travis J Time: 25ms       === > 25ms 
  Niet the Dark Absol Time: === > 30ms
  Arun Saini Time:          === > 31ms
  Mrchief Time:             === > 54ms
  Ivan Nosov Time:          === > 14374ms

Also, I want to mention, since the items are generated randomly, the second place was iterating between Travis and Niet.

Lindholm answered 29/9, 2020 at 3:52 Comment(0)
R
1

Let assume we have data something like this arr=[{id:1,age:17},{id:2,age:19} ...], then we can find unique objects like this -

function getUniqueObjects(ObjectArray) {
    let uniqueIds = new Set();
    const list = [...new Set(ObjectArray.filter(obj => {
        if (!uniqueIds.has(obj.id)) {
            uniqueIds.add(obj.id);
            return obj;
        }
    }))];

    return list;
}

Check here Codepen Link

Redstone answered 13/2, 2021 at 12:30 Comment(0)
G
1

@Travis J dictionary answer in a Typescript type safety functional approach

const uniqueBy = <T, K extends keyof any>(
  list: T[] = [],
  getKey: (item: T) => K,
) => {
  return list.reduce((previous, currentItem) => {
    const keyValue = getKey(currentItem)
    const { uniqueMap, result } = previous
    const alreadyHas = uniqueMap[keyValue]
    if (alreadyHas) return previous
    return {
      result: [...result, currentItem],
      uniqueMap: { ...uniqueMap, [keyValue]: true }
    }
  }, { uniqueMap: {} as Record<K, any>, result: [] as T[] }).result
}

const array = [{ "name": "Joe", "age": 17 }, { "name": "Bob", "age": 17 }, { "name": "Carl", "age": 35 }];

console.log(uniqueBy(array, el => el.age))

// [
//     {
//         "name": "Joe",
//         "age": 17
//     },
//     {
//         "name": "Carl",
//         "age": 35
//     }
// ]
Godden answered 18/11, 2022 at 13:25 Comment(0)
B
1

Presently working on a typescript library to query js objects in an orm fashion. You can download from the below link. This answer explains how to solve using the below library.

https://www.npmjs.com/package/@krishnadaspc/jsonquery?activeTab=readme

var ageArray = 
    [
        {"name":"Joe", "age":17}, 
        {"name":"Bob", "age":17}, 
        {"name":"Carl", "age": 35}
    ]

const ageArrayObj = new JSONQuery(ageArray)
console.log(ageArrayObj.distinct("age").get()) // outputs: [ { name: 'Bob', age: 17 }, { name: 'Carl', age: 35 } ]

console.log(ageArrayObj.distinct("age").fetchOnly("age")) // outputs: [ 17, 35 ]

Runkit live link: https://runkit.com/pckrishnadas88/639b5b3f8ef36f0008b17512

Binder answered 15/12, 2022 at 10:49 Comment(0)
P
1

The simplest way I suggest is by creating an object on top of Array of elements by which you can get all the unique/distinct elements by the field. e.g.

const getDistinct = (arr = [], field) => {
    const distinctMap = {};
    arr.forEach(item => {
        if(!distinctMap[item[field]]) {
            distinctMap[item[field]] = item;
        }
    });
    const distinctFields = Object.keys(distinctMap);
    const distinctElements = Object.values(distinctMap);
    return {distinctFields, distinctElements}
}
const array = 
    [
        {"name":"Joe", "age":17}, 
        {"name":"Bob", "age":17}, 
        {"name":"Carl", "age": 35}
    ]
console.log(JSON.stringify(getDistinct(array, "age")));
/*output will be => 
  {
    distinctFields: ["17", "35"],
    distinctElements: [
      { name: "Joe", age: 17 },
      { name: "Carl", age: 35 }
    ]
  }*/
Prepuce answered 21/3, 2023 at 11:13 Comment(0)
B
0

Using new Ecma features are great but not all users have those available yet.

Following code will attach a new function named distinct to the Global Array object. If you are trying get distinct values of an array of objects, you can pass the name of the value to get the distinct values of that type.

Array.prototype.distinct = function(item){   var results = [];
for (var i = 0, l = this.length; i < l; i++)
    if (!item){
        if (results.indexOf(this[i]) === -1)
            results.push(this[i]);
        } else {
        if (results.indexOf(this[i][item]) === -1)
            results.push(this[i][item]);
    }
return results;};

Check out my post in CodePen for a demo.

Battement answered 28/3, 2017 at 17:59 Comment(2)
This is by far the fastest and most reusable way to do this.Antherozoid
jsbench.github.io/#e6f583e740e107b4f6eabd655114f35d can show how this will run like 70% faster than other methods.Antherozoid
L
0
unique(obj, prop) {
    let result = [];
    let seen = new Set();

    Object.keys(obj)
        .forEach((key) => {
            let value = obj[key];

            let test = !prop
                ? value
                : value[prop];

            !seen.has(test)
                && seen.add(test)
                && result.push(value);
        });

    return result;
}
Linguistics answered 10/10, 2017 at 22:48 Comment(0)
R
0

Well you can use lodash to write a code which will be less verbose

Approach 1:Nested Approach

    let array = 
        [
            {"name":"Joe", "age":17}, 
            {"name":"Bob", "age":17}, 
            {"name":"Carl", "age": 35}
        ]
    let result = _.uniq(_.map(array,item=>item.age))

Approach 2: Method Chaining or Cascading method

    let array = 
        [
            {"name":"Joe", "age":17}, 
            {"name":"Bob", "age":17}, 
            {"name":"Carl", "age": 35}
        ]
    let result = _.chain(array).map(item=>item.age).uniq().value()

You can read about lodash's uniq() method from https://lodash.com/docs/4.17.15#uniq

Royer answered 13/11, 2019 at 7:46 Comment(0)
H
0

The approach for getting a collection of distinct value from a group of keys.

You could take the given code from here and add a mapping for only the wanted keys to get an array of unique object values.

const
    listOfTags = [{ id: 1, label: "Hello", color: "red", sorting: 0 }, { id: 2, label: "World", color: "green", sorting: 1 }, { id: 3, label: "Hello", color: "blue", sorting: 4 }, { id: 4, label: "Sunshine", color: "yellow", sorting: 5 }, { id: 5, label: "Hello", color: "red", sorting: 6 }],
    keys = ['label', 'color'],
    filtered = listOfTags.filter(
        (s => o =>
            (k => !s.has(k) && s.add(k))
            (keys.map(k => o[k]).join('|'))
        )(new Set)
    )
    result = filtered.map(o => Object.fromEntries(keys.map(k => [k, o[k]])));

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Hogg answered 13/11, 2019 at 17:7 Comment(0)
P
0

Using a set and filter. This preserves order:

let unique = (items) => {
    const s = new Set();
    return items.filter((item) => {
      if (s.has(item)) {
        return false;
      }
      s.add(item);
      return true;
    });
  }
  
  console.log(
  unique(
      [
        'one', 'two', 'two', 'three', 'three', 'three'
      ]
    )
  );

/*
output:
[
  "one",
  "two",
  "three"
]
*/
Papilionaceous answered 24/2, 2020 at 21:0 Comment(0)
J
0

If you're stuck using ES5, or you can't use new Set or new Map for some reason, and you need an array containing values with a unique key (and not just an array of unique keys), you can use the following:

function distinctBy(key, array) {
    var keys = array.map(function (value) { return value[key]; });
    return array.filter(function (value, index) { return keys.indexOf(value[key]) === index; });
}

Or the type-safe equivalent in TypeScript:

public distinctBy<T>(key: keyof T, array: T[]) {
    const keys = array.map(value => value[key]);
    return array.filter((value, index) => keys.indexOf(value[key]) === index);
}

Usage:

var distinctPeople = distinctBy('age', people);

All of the other answers either:

  • Return an array of unique keys instead of objects (like returning the list of ages instead of the people who have an unique age);
  • Use ES6, new Set, new Map etc. which might not be available to you;
  • Do not have a configurable key (like having .age hardcoded into the distinct function);
  • Assume the key can be used to index an array, which is not always true and TypeScript wouldn't allow it.

This answers does not have any of the four issues above.

Jampan answered 18/12, 2020 at 10:19 Comment(0)
T
0

If your array is object array, you can use this code.

getUniqueArray = (array: MyData[]) => {
    return array.filter((elem, index) => array.findIndex(obj => obj.value == elem.value) === index);
}

Where MyData is like as below:

export interface MyData{
    value: string,
    name: string
}

Note: You can't use Set because when objects are compared they are compared by reference and not by value. Therefore you need unique key for compare objects, in my example unique key is value field. For more details, you can visit this link : Filter an array for unique values in Javascript

Timid answered 18/1, 2021 at 19:6 Comment(0)
S
0

let mobilePhones = [{id: 1, brand: "B1"}, {id: 2, brand: "B2"}, {id: 3, brand: "B1"}, {id: 4, brand: "B1"}, {id: 5, brand: "B2"}, {id: 6, brand: "B3"}]
 let allBrandsArr = mobilePhones .map(row=>{
        return  row.brand;
      });
let uniqueBrands =   allBrandsArr.filter((item, index, arry) => (arry.indexOf(item) === index));
console.log('uniqueBrands   ', uniqueBrands );
Seraphina answered 20/7, 2021 at 6:32 Comment(0)
C
0

Efficient and clean approach, using iter-ops library:

import {pipe, distinct, map} from 'iter-ops';

const array = 
    [
        {name: 'Joe', age: 17}, 
        {name: 'Bob', age: 17}, 
        {name: 'Carl', age: 35}
    ];

const i = pipe(
    array,
    distinct(a => a.age),
    map(m => m.age)
);

const uniqueAges = [...i]; //=> [17, 35]
Chough answered 16/11, 2021 at 14:49 Comment(0)
S
0

Now we can Unique the Object on the base of same keys and same values

 const arr = [{"name":"Joe", "age":17},{"name":"Bob", "age":17}, {"name":"Carl", "age": 35},{"name":"Joe", "age":17}]
    let unique = []
     for (let char of arr) {
     let check = unique.find(e=> JSON.stringify(e) == JSON.stringify(char))
     if(!check) {
     unique.push(char)
     }
     }
    console.log(unique)

////outPut::: [{ name: "Joe", age: 17 }, { name: "Bob", age: 17 },{ name: "Carl", age: 35 }]

Swiercz answered 21/3, 2022 at 17:54 Comment(0)
S
-1

my two cents on this function:

var result = [];
for (var len = array.length, i = 0; i < len; ++i) {
  var age = array[i].age;
  if (result.indexOf(age) > -1) continue;
  result.push(age);
}

You can see the result here (Method 8) http://jsperf.com/distinct-values-from-array/3

Substandard answered 31/3, 2015 at 13:17 Comment(0)
P
-1

If you would like to filter out duplicate values from an array on a known unique object property, you could use the following snippet:

let arr = [
  { "name": "Joe", "age": 17 },
  { "name": "Bob", "age": 17 },
  { "name": "Carl", "age": 35 },
  { "name": "Carl", "age": 35 }
];

let uniqueValues = [...arr.reduce((map, val) => {
    if (!map.has(val.name)) {
        map.set(val.name, val);
    }
    return map;
}, new Map()).values()]
Pace answered 21/9, 2019 at 17:43 Comment(1)
I don't know why this answer got down voted. You can change map.set(val.name, val); statement to match your desired distinct output including the object itself as in the example or a property of it like this: map.set(val.name, val.name);Pace
G
-2

this function can unique array and object

function oaunic(x,n=0){
    if(n==0) n = "elem";
    else n = "elem."+n;
    var uval = [];
    var unic = x.filter(function(elem, index, self){
        if(uval.indexOf(eval(n)) < 0){
            uval.push(eval(n));
            return index == self.indexOf(elem);
        }
    })
    return unic;
}

use that like this

tags_obj = [{name:"milad"},{name:"maziar"},{name:"maziar"}]
tags_arr = ["milad","maziar","maziar"]
console.log(oaunic(tags_obj,"name")) //for object
console.log(oaunic(tags_arr)) //for array
Glenoid answered 9/11, 2017 at 6:9 Comment(0)
J
-2

I know this is an old and relatively well-answered question and the answer I'm giving will get the complete-object back (Which I see suggested in a lot of the comments on this post). It may be "tacky" but in terms of readability seems a lot cleaner (although less efficient) than a lot of other solutions.

This will return a unique array of the complete objects inside the array.

let productIds = data.map(d => { 
   return JSON.stringify({ 
      id    : d.sku.product.productId,
      name  : d.sku.product.name,
      price : `${d.sku.product.price.currency} ${(d.sku.product.price.gross / d.sku.product.price.divisor).toFixed(2)}`
   })
})
productIds = [ ...new Set(productIds)].map(d => JSON.parse(d))```
Jocularity answered 14/7, 2019 at 8:40 Comment(1)
Not JSON.stringify, but rather npmjs.com/package/json-stable-stringify or npmjs.com/package/es6-json-stable-stringify is needed. JSON.parse is OK.Borman
L
-6

using d3.js v3:

  ages = d3.set(
    array.map(function (d) { return d.age; })
  ).values();
Legalize answered 26/7, 2017 at 11:52 Comment(1)
Why to use D3 when the same function is available in pure JS?Onomasiology

© 2022 - 2024 — McMap. All rights reserved.