32

I have an array of objects that I would like to trim down based on a specific key:value pair. I want to create an array that includes only one object per this specific key:value pair. It doesn't necessarily matter which object of the duplicates is copied to the new array.

For example, I want to trim based on the price property of arrayWithDuplicates, creating a new array that only includes one of each value:

var arrayWithDuplicates = [
  {"color":"red", 
    "size": "small",
    "custom": {
      "inStock": true,
      "price": 10
    }
  },
  {"color":"green", 
    "size": "small",
    "custom": {
      "inStock": true,
      "price": 30
    }
  },
  {"color":"blue", 
    "size": "medium",
    "custom": {
      "inStock": true,
      "price": 30
    }
  },
  {"color":"red", 
    "size": "large",
    "custom": {
      "inStock": true,
      "price": 20
    }
  }
];

Would become:

var trimmedArray = [
  {"color":"red", 
    "size": "small",
    "custom": {
      "inStock": true,
      "price": 10
    }
  },
  {"color":"green", 
    "size": "small",
    "custom": {
      "inStock": true,
      "price": 30
    }
  },
  {"color":"red", 
    "size": "large",
    "custom": {
      "inStock": true,
      "price": 20
    }
  }
];

Is there a JavaScript or Angular function that would loop through and do this?

EDIT: The property to filter on is nested within another property.

Lauren F
  • 1,282
  • 4
  • 18
  • 29
  • possible duplicate of [Remove duplicates from an array of objects in javascript](http://stackoverflow.com/questions/2218999/remove-duplicates-from-an-array-of-objects-in-javascript) – Geoherna Aug 27 '15 at 00:18
  • are you looking something like _.map or _.pluck function in underscore? http://underscorejs.org/#pluck – ncubica Aug 27 '15 at 00:20

12 Answers12

38

This function removes duplicate values from an array by returning a new one.

function removeDuplicatesBy(keyFn, array) {
    var mySet = new Set();
    return array.filter(function(x) {
        var key = keyFn(x), isNew = !mySet.has(key);
        if (isNew) mySet.add(key);
        return isNew;
    });
}

var values = [{color: "red"}, {color: "blue"}, {color: "red", number: 2}];
var withoutDuplicates = removeDuplicatesBy(x => x.color, values);
console.log(withoutDuplicates); // [{"color": "red"}, {"color": "blue"}]

So you could use it like

var arr = removeDuplicatesBy(x => x.custom.price, yourArrayWithDuplicates);
Tamas Hegedus
  • 28,755
  • 12
  • 63
  • 97
  • Thank you! This definitely worked, but I just edited my question to reflect that this needs to filter on nested properties (massive miss on my end). Could your function handle that? – Lauren F Aug 27 '15 at 00:50
  • Thanks, good answer, but obviously if using ES6 you may run into issues in older browsers without some kind of shim or polyfill (which is not possible for arrow operator as far as I know since it's a syntax error). To make it work with antique browsers like IE11 just use function: `removeDuplicatesBy(function(x) { return x.custom.price; }, yourArrayWithDuplicates);` – nothingisnecessary Nov 15 '17 at 23:07
12

I don't think there's a built-in function in Angular, but it isn't hard to create one:

function removeDuplicates(originalArray, objKey) {
  var trimmedArray = [];
  var values = [];
  var value;

  for(var i = 0; i < originalArray.length; i++) {
    value = originalArray[i][objKey];

    if(values.indexOf(value) === -1) {
      trimmedArray.push(originalArray[i]);
      values.push(value);
    }
  }

  return trimmedArray;

}

Usage:

removeDuplicates(arrayWithDuplicates, 'size');

Returns:

[
    {
        "color": "red",
        "size": "small"
    },
    {
        "color": "blue",
        "size": "medium"
    },
    {
        "color": "red",
        "size": "large"
    }
]

And

removeDuplicates(arrayWithDuplicates, 'color');

Returns:

[
    {
        "color": "red",
        "size": "small"
    },
    {
        "color": "green",
        "size": "small"
    },
    {
        "color": "blue",
        "size": "medium"
    }
]
yvesmancera
  • 2,915
  • 5
  • 24
  • 33
  • what if there are 2 properties you are checking for uniqueness e.g. {a: 1, b:1}, and if any other objects in array have this - it's a duplicate. – zero_cool Jul 12 '18 at 21:00
9

Use Array.filter(), keeping track of values by using an Object as a hash, and filtering out any items whose value is already contained in the hash.

function trim(arr, key) {
    var values = {};
    return arr.filter(function(item){
        var val = item[key];
        var exists = values[val];
        values[val] = true;
        return !exists;
    });
}
gilly3
  • 87,962
  • 25
  • 144
  • 176
6

You can use underscore for this:

//by size:
var uSize = _.uniqBy(arrayWithDuplicates, function(p){ return p.size; });

//by custom.price;
var uPrice = _.uniqBy(arrayWithDuplicates, function(p){ return p.custom.price; });
A. El-zahaby
  • 1,130
  • 11
  • 32
user3335966
  • 2,673
  • 4
  • 30
  • 33
  • 3
    It has been changed to _.uniqBy and it gets the 'unique' property name. _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x'); // => [{ 'x': 1 }, { 'x': 2 }] – natansun Jun 07 '17 at 15:14
4

You can use lodash to remove duplicate objects:

 import * as _ from 'lodash';
  _.uniqBy(data, 'id');

Here 'id' is your unique identifier

Rajat Jain
  • 1,339
  • 2
  • 16
  • 29
2

Try the following function:

function trim(items){
    const ids = [];
    return items.filter(item => ids.includes(item.id) ? false : ids.push(item.id));
}
ivcubr
  • 1,988
  • 9
  • 20
  • 28
sliter
  • 71
  • 1
  • 6
1

Simple solution although not the most performant:

var unique = [];
duplicates.forEach(function(d) {
    var found = false;
    unique.forEach(function(u) {
        if(u.key == d.key) {
            found = true;
        }
    });
    if(!found) {
        unique.push(d);
    }
});
nbrooks
  • 18,126
  • 5
  • 54
  • 66
Souvik Basu
  • 3,069
  • 4
  • 28
  • 42
1

using lodash you can filter it out easily

the first parameter will be your array and second will be your field with duplicates

_.uniqBy(arrayWithDuplicates, 'color')

it will return an array with unique value

javaDeveloper
  • 1,403
  • 3
  • 28
  • 42
yureka
  • 31
  • 8
1
for (let i = 0; i < arrayWithDuplicates.length; i++) {
     for (let j = i + 1; j < arrayWithDuplicates.length; j++) {
       if (arrayWithDuplicates[i].name === students[j].name) {
          arrayWithDuplicates.splice(i, 1);
       }
     }
    }

this will work perfectly...and this will delete first repeated array.
To delete last repeated array we only have to change
 arrayWithDuplicates.splice(i, 1) ; into
 arrayWithDuplicates.splice(j, 1);
0

Off the top of my head there is no one function that will do this for you as you are dealing with an array of objects and also there is no rule for which duplicate would be removed as duplicate.

In your example you remove the one with size: small but if you were to implement this using a loop you'd most likely include the first and exclude the last as you loop through your array.

It may very well be worth taking a look at a library such as lodash and creating a function that uses a combination of it's API methods to get the desired behaviour you want.

Here is a possible solution you could use making use of basic Arrays and a filter expression to check whether a new item would be considered a duplicate before being attached to a return result.

var arrayWithDuplicates = [
    {"color":"red", "size": "small"},
    {"color":"green", "size": "small"},
    {"color":"blue", "size": "medium"},
    {"color":"red", "size": "large"}
];

var reduce = function(arr, prop) {
  var result = [],
      filterVal,
      filters,
      filterByVal = function(n) {
          if (n[prop] === filterVal) return true;
      };
  for (var i = 0; i < arr.length; i++) {
      filterVal = arr[i][prop];
      filters   = result.filter(filterByVal);
      if (filters.length === 0) result.push(arr[i]);
  }
  return result;
};

console.info(reduce(arrayWithDuplicates, 'color'));

You can check out some literature on Array filtering here If you need to provide a preference on which item to remove you could define extra parameters and logic that will make extra property checks before adding to a return value.

Hope that helps!

Jhey
  • 1,377
  • 7
  • 10
0

Here is the typescript way

    public removeDuplicates(originalArray:any[], prop) {
    let newArray = [];
    let lookupObject = {};

    originalArray.forEach((item, index) => {
        lookupObject[originalArray[index][prop]] = originalArray[index];
    });

    Object.keys(lookupObject).forEach(element => {
        newArray.push(lookupObject[element]);
    });
    return newArray;
}

And

let output = this.removeDuplicates(yourArray,'color');
Praveen M P
  • 11,314
  • 7
  • 34
  • 41
0

This is just another 'feature' based on yvesmancera's solution (after I started tinkering for my own solution) Also noted we are only allowed to currently use IE 11, so limited ES5 is allowed.

var newArray = RemoveDuplicates(myArray,'Role', 2);

function RemoveDuplicates(array, objKey, rtnType) {
var list = [], values = [], value;
for (var i = 0; i < array.length; i++) {
    value = array[i][objKey];
    if(values.indexOf(value) === -1){
        list.push(array[i]);
        values.push(value);
        }
    }
    if(rtnType == 1)
        return list;
    return values;
};

Hoping this will work for most, if not all arrays when filtering out objects based on a single object property value.

Keltanis
  • 124
  • 1
  • 1
  • 9