23

Let's say I start with this:

var shippingAddresses = [
    {
      "firstname": "Kevin",
      "lastname": "Borders",
      "address1": "2201 N Pershing Dr",
      "address2": "Apt 417",
      "city": "Arlington",
      "state": "VA",
      "zip": "22201",
      "country": "US"
    }, 
    {
      "firstname": "Dan",
      "lastname": "Hess",
      "address1": "304 Riversedge Dr",
      "address2": "",
      "city": "Saline",
      "state": "MI",
      "zip": "48176",
      "country": "US"
    }
]

I use this to prepopulate a form.
Users can edit entries or add new ones. I need to prevent them from adding duplicates.

The issue is that the structure of the form that I am serializing and the order these values are returned from the database are not the same, so there is a chance that I will insert an item into this array with the following format:

{
  "country": "US",
  "firstname": "Kevin",
  "lastname": "Borders",
  "address1": "2201 N Pershing Dr",
  "address2": "Apt 417",
  "zip": "22201",                                    
  "city": "Arlington",
  "state": "VA"
}

Which is the same as the first entry, just ordered differently.

I am loading underscorejs, so if there's a way to handle it with that library that would be great. I'm also using jQuery if that helps.

At this point I'm not sure how to proceed.

vsync
  • 118,978
  • 58
  • 307
  • 400
S16
  • 2,963
  • 9
  • 40
  • 64
  • Do you need to sort properties of objects with _? – YD1m Jun 27 '13 at 18:16
  • I don't need to sort the properties, no. I'm specifically trying to do this in a way that the order of the properties is irrelevant. – S16 Jun 27 '13 at 18:21
  • Are you really comparing the serialized JSON strings against each other or why do you think the order matters? Show us the code you're using to prevent duplicates – Bergi Jun 27 '13 at 18:29
  • I'm not preventing duplicates right now, that's why I posted this question. – S16 Jun 27 '13 at 18:37
  • ok, the new edit to my answer should work now with objects of variable property positioning. – Matthew Graves Jun 27 '13 at 18:46

7 Answers7

31

The Underscore findWhere function does exactly what you need - it's not an indexOf search by object identity, but searches objects whose properties have the same values as the input.

if (_.findWhere(shippingAddresses, toBeInserted) == null) {
    shippingAddresses.push(toBeInserted);
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I think this is the best way to go. Especially considering that you've got underscore. I could refactor my code to work without underscore, but this one is the way to do. – Matthew Graves Jun 27 '13 at 23:36
  • 2
    This code behaves correctly thanks to lenient comparison `==` but `_.findWhere` has to be tested against `undefined` and not `null` (as per [underscore.js docs](http://underscorejs.org/#findWhere)). – physiocoder May 29 '14 at 16:58
  • 2
    @physiocoder: Just what I intended :-) Of course you can also use `=== undefined` if you like that better. – Bergi May 29 '14 at 16:59
17

Basic example using lodash union method:

var a = [1,2,3];

// try to add "1" and "4" to the above Array
a = _.union(a, [1, 4]);

console.log(a);
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.min.js"></script>

While this doesn't directly answers the question, it does answers the broader question of how to add unique values to an Array, and like myself, others might stumble upon this page from google.

Community
  • 1
  • 1
vsync
  • 118,978
  • 58
  • 307
  • 400
5

based on this answer to: "js-remove-an-array-element-by-index-in-javascript"

https://stackoverflow.com/a/7142909/886092

I'm using the following idiom that is concise and does not require underscore.js or any other framework.

Here is an example for recording selected and deselected rows for DataTables jquery plugin. I keep an array of currently selected ids, and I don't want to end up with duplicates in the array:

in coffeescript

  fnRowSelected: (nodes) ->
    position = $selected.indexOf(nodes[0].id)
    unless ~position
      $selected.push nodes[0].id
    return
  fnRowDeselected: (nodes) ->
    position = $selected.indexOf(nodes[0].id)
    if ~position
      $selected.splice(position, 1)

More generally it would

position = myArray.indexOf(myval)
unless ~position
  myArray.push myVal

or in JS

var position;

position = myArray.indexOf(myval);

if (!~position) {
  myArray.push(myVal);
}
Community
  • 1
  • 1
Ashley Raiteri
  • 700
  • 8
  • 17
  • 3
    perhaps you might garner more upvotes if you answered in JS instead of coffeescript, since the question clearly asked about doing it in JS :) – Jason J. Nathan Jun 06 '15 at 08:40
1

EDIT, this will work with your example of unsorted properties:

var normalized_array = _.map(shippingAddresses, function(a){ 
      var o = {}; 
      _.each(Object.keys(shippingAddresses[0]), function(x){o[x] = a[x]});
      return o;
})
var stringy_array = _.map(normalized_array, JSON.stringify);
shippingAddresses = _.map(_.uniq(stringy_array), JSON.parse});

and we could do this with a one-liner but it would be super ugly:

shippingAddresses_uniq = _.map(_.uniq(_.map(_.map(shippingAddresses, function(a){ var o = {}; _.each(Object.keys(shippingAddresses[0]), function(x){o[x] = a[x]}); return o; }), JSON.stringify)), JSON.parse});
Matthew Graves
  • 3,226
  • 1
  • 17
  • 20
1

If you want to check the user input object you could try this function:

var uniqueInput = {
                       "country": "UK",
                       "firstname": "Calvin",
                       "lastname": "Borders",
                       "address1": "2201 N Pershing Dr",
                       "address2": "Apt 417",
                       "city": "Arlington",
                       "state": "VA",
                       "zip": "22201"

                        };

var duplicatedInput = {
                       "country": "US",
                       "firstname": "Kevin",
                       "lastname": "Borders",
                       "address1": "2201 N Pershing Dr",
                       "address2": "Apt 417",
                       "city": "Arlington",
                       "state": "VA",
                       "zip": "22201"

                        };

var shippingAddresses = [{
                       "firstname": "Kevin",
                       "lastname": "Borders",
                       "address1": "2201 N Pershing Dr",
                       "address2": "Apt 417",
                       "city": "Arlington",
                       "state": "VA",
                       "zip": "22201",
                       "country": "US"
                        }, {
                            "firstname": "Dan",
                            "lastname": "Hess",
                            "address1": "304 Riversedge Dr",
                            "address2": "",
                            "city": "Saline",
                            "state": "MI",
                            "zip": "48176",
                            "country": "US"
                        }];

function checkDuplication(checkTarget,source){
    _.each(source,function(obj){
        if(_.isEqual(checkTarget,obj)){ 
            alert("duplicated");
        }
    });
}

And try to invoke this check function in different parameter (uniqueInput and duplicatedInput) I think it could check the duplication input in your shipping addresses.

checkDuplication(uniqueInput,shippingAddresses);
checkDuplication(duplicatedInput,shippingAddresses);

I make a jsfiddle. You could try it. Hope this is helpful for you.

Chickenrice
  • 5,727
  • 2
  • 22
  • 21
1

I think you need this,

NOTE: No library is required.

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

function addUniqeObj(data) {
  let index = -1;

  for(let i = 0, i < array.length; i++) {
    if(array[i].id === data.id) {
      index = i;
    }
  }

  if(index > -1) {
    array[index] = data;
  } else {
    array.push(data)
  }

}
Rohit Nishad
  • 2,570
  • 2
  • 22
  • 32
-1

Basic example using Set() from ECMAScript 2015 (no library required)

The Set object lets you store unique values of any type (whether primitive values or object references). If an iterable object is passed, all of its elements will be added to the new Set. Here I'll just add one value:

// original array with duplicates already present
const numbers = [1, 1, 1, 2, 3, 100]

// Use Set to remove duplicate elements from the array 
// and keep your new addition from causing a duplicate.

// New value (100) is not added since it exists (and array 
// also is de-duped)
console.log(Array.from(new Set([...numbers, 100])))
// [1, 2, 3, 100]

// New, non-existing value (101) is added (and array is de-duped)
console.log(Array.from(new Set([...numbers, 101])))
// [1, 2, 3, 100, 101]
Community
  • 1
  • 1
Paul Sturm
  • 2,118
  • 1
  • 18
  • 23
  • 3
    The question is specifically asking for `objects`, not `integers`. When performing addition to a `Set`, it will still consider every new object added as unique because of its reference. – raisinrising Apr 09 '20 at 12:02
  • also downvoting because question ask for object... – pjdupreez Feb 03 '22 at 16:56