Vergleichen von Arrays von Objekten in JavaScript

71

Ich möchte 2 Arrays von Objekten in JavaScript-Code vergleichen. Die Objekte haben insgesamt 8 Eigenschaften, aber jedes Objekt hat keinen Wert für jedes Objekt, und die Arrays werden niemals größer als jeweils 8 Elemente sein. Vielleicht die Brute-Force-Methode, um jedes Objekt zu durchlaufen und dann die Werte von zu betrachten 8 Eigenschaften sind der einfachste Weg, um das zu tun, was ich tun möchte, aber vor der Implementierung wollte ich sehen, ob jemand eine elegantere Lösung hat. Irgendwelche Gedanken?

AdamB
quelle

Antworten:

53

BEARBEITEN: Sie können Operatoren in aktuellen, gängigen browserbasierten Implementierungen von JavaScript-Interpreten nicht überladen.

Um die ursprüngliche Frage zu beantworten, können Sie dies auf eine Art und Weise tun, und wohlgemerkt, dies ist ein kleiner Hack. Serialisieren Sie einfach die beiden Arrays mit JSON und vergleichen Sie dann die beiden JSON-Zeichenfolgen. Das würde Ihnen einfach sagen, ob die Arrays unterschiedlich sind. Natürlich können Sie dies auch für jedes der Objekte in den Arrays tun, um zu sehen, welche unterschiedlich sind.

Eine andere Möglichkeit ist die Verwendung einer Bibliothek, die einige nützliche Funktionen zum Vergleichen von Objekten bietet . Ich verwende und empfehle MochiKit .


EDIT: Die Antwort, die kamens gab, verdient ebenfalls Beachtung, da eine einzelne Funktion zum Vergleichen zweier gegebener Objekte viel kleiner wäre als jede Bibliothek, um das zu tun, was ich vorschlage (obwohl mein Vorschlag sicherlich gut genug funktionieren würde).

Hier ist eine naive Implementierung, die möglicherweise gerade genug für Sie tut - beachten Sie, dass bei dieser Implementierung potenzielle Probleme auftreten:

function objectsAreSame(x, y) {
   var objectsAreSame = true;
   for(var propertyName in x) {
      if(x[propertyName] !== y[propertyName]) {
         objectsAreSame = false;
         break;
      }
   }
   return objectsAreSame;
}

Die Annahme ist, dass beide Objekte dieselbe exakte Liste von Eigenschaften haben.

Oh, und es ist wahrscheinlich offensichtlich, dass ich zum Guten oder Schlechten zum einzigen Lager mit einem Rückgabepunkt gehöre. :) :)

Jason Bunting
quelle
2
Nur um auf die Einschränkung hinzuweisen: Dies scheint beim Vergleich von Objekten, die Objekte enthalten, fehlzuschlagen. (Und wie Sie erwähnen, wird es fehlschlagen, wenn die beiden Objekte nicht "die gleiche genaue Liste von Eigenschaften" haben, wie yes eine Supermenge von sein darf x.
Alan H.
4
Eine Einschränkung in Bezug auf den Vorschlag zur JSON-Serialisierung besteht darin, dass die JSON-Serialisierung nicht funktioniert, wenn Sie Objekte (keine Arrays) vergleichen und sich nicht um die Reihenfolge kümmern (z. B. benannte Schlüssel, kein numerisches Array).
Alan H.
@ AlanH. Meinen Sie damit JSON.stringify, dass zwei Nicht-Objekt-Arrays (z. B. Number, String) sowie zwei Object-Arrays verglichen werden sollen, nicht jedoch zwei Objekte? Wenn ja warum? insb. beim Vergleich zweier Objekt-Arrays?
Zyxue
2
Vergessen Sie Arrays von Objekten und Objektarrays, was auch immer das bedeutet. Denken Sie nur an {a: 1, b: 1} und {b: 1, a: 1}. Die Reihenfolge der Objekte ist uns egal, aber diese Zeichenfolgen unterscheiden sich deutlich.
Alan H.
30

Da die Serialisierung im Allgemeinen nicht funktioniert (nur wenn die Reihenfolge der Eigenschaften übereinstimmt JSON.stringify({a:1,b:2}) !== JSON.stringify({b:2,a:1}):), müssen Sie die Anzahl der Eigenschaften überprüfen und auch jede Eigenschaft vergleichen:

const objectsEqual = (o1, o2) =>
    Object.keys(o1).length === Object.keys(o2).length 
        && Object.keys(o1).every(p => o1[p] === o2[p]);

const obj1 = { name: 'John', age: 33};
const obj2 = { age: 33, name: 'John' };
const obj3 = { name: 'John', age: 45 };
        
console.log(objectsEqual(obj1, obj2)); // true
console.log(objectsEqual(obj1, obj3)); // false

Wenn Sie einen umfassenden Vergleich benötigen, können Sie die Funktion rekursiv aufrufen:

const obj1 = { name: 'John', age: 33, info: { married: true, hobbies: ['sport', 'art'] } };
const obj2 = { age: 33, name: 'John', info: { hobbies: ['sport', 'art'], married: true } };
const obj3 = { name: 'John', age: 33 };

const objectsEqual = (o1, o2) => 
    typeof o1 === 'object' && Object.keys(o1).length > 0 
        ? Object.keys(o1).length === Object.keys(o2).length 
            && Object.keys(o1).every(p => objectsEqual(o1[p], o2[p]))
        : o1 === o2;
        
console.log(objectsEqual(obj1, obj2)); // true
console.log(objectsEqual(obj1, obj3)); // false

Dann ist es einfach, diese Funktion zu verwenden, um Objekte in Arrays zu vergleichen:

const arr1 = [obj1, obj1];
const arr2 = [obj1, obj2];
const arr3 = [obj1, obj3];

const arraysEqual = (a1, a2) => 
   a1.length === a2.length && a1.every((o, idx) => objectsEqual(o, a2[idx]));

console.log(arraysEqual(arr1, arr2)); // true
console.log(arraysEqual(arr1, arr3)); // false
ttulka
quelle
2
Beste Antwort. Prägnant und perfekt. Sollte oben sein.
IsmailS
Großartig. Schließen Sie einfach den tiefen Vergleich mit der Validierung ab, um mit der Eigenschaft null zu arbeiten, oder geben Sie zurück o1 === o2. Cool.
Sokrates Tuas
21

Ich weiß, dass dies eine alte Frage ist und die Antworten gut funktionieren ... aber dies ist etwas kürzer und erfordert keine zusätzlichen Bibliotheken (dh JSON):

function arraysAreEqual(ary1,ary2){
  return (ary1.join('') == ary2.join(''));
}
jwood
quelle
14
Das OP wollte Arrays von Objekten verbinden. Dies funktioniert nur für Arrays von Skalaren.
Jonathan M
10
Es ist auch zerbrechlich. Wenn: a=["1,2"] , b=["1", "2"]dann führt ein join()auf den zwei verschiedenen Arrays zu'1,2'
Jason Moore
@ Jason Moore nicht wahr, a.join ('') // => "1,2"; b.join ('') // => "12"
Ernest
9
Sie haben Recht mit diesem Beispiel, aber es ist immer noch fragil. a=["12"], b=["1", "2"]führt zu "12"=="12"und ich glaube nicht, dass ein Trennzeichen Sie retten kann, weil es im Objekt selbst sein könnte. Eine Längenprüfung kann dies auch nicht beheben, daa=["12", "3"], b=["1", "23"]
Qsario
5
Eine etwas robustere Implementierung:return ary1.join(',') === ary2.join(',');
Brice Roncace
18

Ehrlich gesagt, mit maximal 8 Objekten und maximal 8 Eigenschaften pro Objekt ist es am besten, einfach jedes Objekt zu durchlaufen und die Vergleiche direkt durchzuführen. Es wird schnell und einfach sein.

Wenn Sie diese Art von Vergleichen häufig verwenden, stimme ich Jason in Bezug auf die JSON-Serialisierung zu. Andernfalls müssen Sie Ihre App nicht mit einer neuen Bibliothek oder einem neuen JSON-Serialisierungscode verlangsamen.

Kamens
quelle
38
"... ich stimme Jason in Bezug auf JSON zu ..." +1 nur dafür! ;-)
Cerebrus
16

Ich habe ein wenig an einem einfachen Algorithmus gearbeitet, um den Inhalt zweier Objekte zu vergleichen und eine verständliche Liste von Unterschieden zurückzugeben. Ich dachte, ich würde teilen. Es leiht sich einige Ideen für jQuery aus, nämlich diemap Funktionsimplementierung und die Überprüfung des Objekt- und Array-Typs.

Es wird eine Liste von "diff-Objekten" zurückgegeben, bei denen es sich um Arrays mit den diff-Informationen handelt. Es ist sehr einfach.

Hier ist es:

// compare contents of two objects and return a list of differences
// returns an array where each element is also an array in the form:
// [accessor, diffType, leftValue, rightValue ]
//
// diffType is one of the following:
//   value: when primitive values at that index are different
//   undefined: when values in that index exist in one object but don't in 
//              another; one of the values is always undefined
//   null: when a value in that index is null or undefined; values are
//         expressed as boolean values, indicated wheter they were nulls
//   type: when values in that index are of different types; values are 
//         expressed as types
//   length: when arrays in that index are of different length; values are
//           the lengths of the arrays
//

function DiffObjects(o1, o2) {
    // choose a map() impl.
    // you may use $.map from jQuery if you wish
    var map = Array.prototype.map?
        function(a) { return Array.prototype.map.apply(a, Array.prototype.slice.call(arguments, 1)); } :
        function(a, f) { 
            var ret = new Array(a.length), value;
            for ( var i = 0, length = a.length; i < length; i++ ) 
                ret[i] = f(a[i], i);
            return ret.concat();
        };

    // shorthand for push impl.
    var push = Array.prototype.push;

    // check for null/undefined values
    if ((o1 == null) || (o2 == null)) {
        if (o1 != o2)
            return [["", "null", o1!=null, o2!=null]];

        return undefined; // both null
    }
    // compare types
    if ((o1.constructor != o2.constructor) ||
        (typeof o1 != typeof o2)) {
        return [["", "type", Object.prototype.toString.call(o1), Object.prototype.toString.call(o2) ]]; // different type

    }

    // compare arrays
    if (Object.prototype.toString.call(o1) == "[object Array]") {
        if (o1.length != o2.length) { 
            return [["", "length", o1.length, o2.length]]; // different length
        }
        var diff =[];
        for (var i=0; i<o1.length; i++) {
            // per element nested diff
            var innerDiff = DiffObjects(o1[i], o2[i]);
            if (innerDiff) { // o1[i] != o2[i]
                // merge diff array into parent's while including parent object name ([i])
                push.apply(diff, map(innerDiff, function(o, j) { o[0]="[" + i + "]" + o[0]; return o; }));
            }
        }
        // if any differences were found, return them
        if (diff.length)
            return diff;
        // return nothing if arrays equal
        return undefined;
    }

    // compare object trees
    if (Object.prototype.toString.call(o1) == "[object Object]") {
        var diff =[];
        // check all props in o1
        for (var prop in o1) {
            // the double check in o1 is because in V8 objects remember keys set to undefined 
            if ((typeof o2[prop] == "undefined") && (typeof o1[prop] != "undefined")) {
                // prop exists in o1 but not in o2
                diff.push(["[" + prop + "]", "undefined", o1[prop], undefined]); // prop exists in o1 but not in o2

            }
            else {
                // per element nested diff
                var innerDiff = DiffObjects(o1[prop], o2[prop]);
                if (innerDiff) { // o1[prop] != o2[prop]
                    // merge diff array into parent's while including parent object name ([prop])
                    push.apply(diff, map(innerDiff, function(o, j) { o[0]="[" + prop + "]" + o[0]; return o; }));
                }

            }
        }
        for (var prop in o2) {
            // the double check in o2 is because in V8 objects remember keys set to undefined 
            if ((typeof o1[prop] == "undefined") && (typeof o2[prop] != "undefined")) {
                // prop exists in o2 but not in o1
                diff.push(["[" + prop + "]", "undefined", undefined, o2[prop]]); // prop exists in o2 but not in o1

            }
        }
        // if any differences were found, return them
        if (diff.length)
            return diff;
        // return nothing if objects equal
        return undefined;
    }
    // if same type and not null or objects or arrays
    // perform primitive value comparison
    if (o1 != o2)
        return [["", "value", o1, o2]];

    // return nothing if values are equal
    return undefined;
}
Yuval
quelle
13

Ich habe versucht JSON.stringify()und für mich gearbeitet.

let array1 = [1,2,{value:'alpha'}] , array2 = [{value:'alpha'},'music',3,4];

JSON.stringify(array1) // "[1,2,{"value":"alpha"}]"

JSON.stringify(array2) // "[{"value":"alpha"},"music",3,4]"

JSON.stringify(array1) === JSON.stringify(array2); // false
Sanjay Verma
quelle
17
Zu beachten hier - dies wird nicht funktionieren, wenn die Objekteigenschaften nicht in Ordnung sind
Ganeshk
Zuerst können wir das Array sortieren und dann stringify verwenden
Ehsan sarshar
@Ehsansarshar, das nicht funktioniert ... Sie müssen alle Objekteigenschaften und Arrays sortieren ...
Christian
1

Probieren Sie mal das hier an:

function used_to_compare_two_arrays(a, b)
{
  // This block will make the array of indexed that array b contains a elements
  var c = a.filter(function(value, index, obj) {
    return b.indexOf(value) > -1;
  });

  // This is used for making comparison that both have same length if no condition go wrong 
  if (c.length !== a.length) {
    return 0;
  } else{
    return 1;
  }
}
Keshav Kalra
quelle
ähm ... die if-Anweisung kann einfach vereinfacht werdenreturn c.length === a.length;
Christian
1

Hier ist mein Versuch, das Assert-Modul + Node- Paket- Objekt-Hash von Node zu verwenden .

Ich nehme an, Sie möchten überprüfen, ob zwei Arrays dieselben Objekte enthalten, auch wenn diese Objekte zwischen den beiden Arrays unterschiedlich angeordnet sind.

var assert = require('assert');
var hash = require('object-hash');

var obj1 = {a: 1, b: 2, c: 333},
    obj2 = {b: 2, a: 1, c: 444},
    obj3 = {b: "AAA", c: 555},
    obj4 = {c: 555, b: "AAA"};

var array1 = [obj1, obj2, obj3, obj4];
var array2 = [obj3, obj2, obj4, obj1]; // [obj3, obj3, obj2, obj1] should work as well

// calling assert.deepEquals(array1, array2) at this point FAILS (throws an AssertionError)
// even if array1 and array2 contain the same objects in different order,
// because array1[0].c !== array2[0].c

// sort objects in arrays by their hashes, so that if the arrays are identical,
// their objects can be compared in the same order, one by one
var array1 = sortArrayOnHash(array1);
var array2 = sortArrayOnHash(array2);

// then, this should output "PASS"
try {
    assert.deepEqual(array1, array2);
    console.log("PASS");
} catch (e) {
    console.log("FAIL");
    console.log(e);
}

// You could define as well something like Array.prototype.sortOnHash()...
function sortArrayOnHash(array) {
    return array.sort(function(a, b) {
        return hash(a) > hash(b);
    });
}
Frosty Z.
quelle
1

Die objectsAreSamein der Antwort von @ JasonBunting erwähnte Funktion funktioniert für mich einwandfrei. Es gibt jedoch ein kleines Problem: Wenn x[propertyName]und y[propertyName]Objekte ( typeof x[propertyName] == 'object') sind, müssen Sie die Funktion rekursiv aufrufen, um sie zu vergleichen.

Alexander Elgin
quelle
0

Verwendung _.somevon lodash: https://lodash.com/docs/4.17.11#some

const array1AndArray2NotEqual = 
          _.some(array1, (a1, idx) => a1.key1 !== array2[idx].key1 
                                     || a1.key2 !== array2[idx].key2 
                                     || a1.key3 !== array2[idx].key3);
Henry Sellars
quelle