Bagaimana cara melakukan perbandingan yang mendalam antara 2 objek dengan lodash?


309

Saya memiliki 2 objek bersarang yang berbeda dan saya perlu tahu apakah mereka memiliki perbedaan dalam salah satu properti bersarang mereka.

var a = {};
var b = {};

a.prop1 = 2;
a.prop2 = { prop3: 2 };

b.prop1 = 2;
b.prop2 = { prop3: 3 };

Objek bisa menjadi jauh lebih kompleks dengan properti yang lebih bersarang. Tapi yang ini adalah contoh yang bagus. Saya memiliki opsi untuk menggunakan fungsi rekursif atau sesuatu dengan lodash ...


Untuk perbandingan lebih dalam, stackoverflow.com/a/46003894/696535
Pawel

7
_.isEqual(value, other)Melakukan perbandingan mendalam antara dua nilai untuk menentukan apakah mereka setara. lodash.com/docs#isEqual
Lukas Liesis

JSON.stringify ()
xgqfrms

10
JSON.stringify () salah: JSON.stringify ({a: 1, b: 2})! == JSON.stringify ({b: 2, a: 1})
Shl

Jawaban:


475

Solusi yang mudah dan elegan adalah menggunakan _.isEqual, yang melakukan perbandingan mendalam:

var a = {};
var b = {};

a.prop1 = 2;
a.prop2 = { prop3: 2 };

b.prop1 = 2;
b.prop2 = { prop3: 3 };

_.isEqual(a, b); // returns false if different

Namun, solusi ini tidak menunjukkan properti mana yang berbeda.

http://jsfiddle.net/bdkeyn0h/


2
Saya tahu bahwa jawabannya cukup lama, tetapi saya ingin menambahkan, itu _.isEqualbisa sangat rumit. Jika Anda menyalin objek dan mengubah beberapa nilai di sana itu akan tetap terlihat benar, karena rujukannya sama. Jadi seseorang harus berhati-hati menggunakan fungsi ini.
oruckdeschel

23
@oruckdeschel jika referensi sama, itu adalah objek yang sama. karenanya itu sama. ini adalah penunjuk yang rumit bukan lodash. lodash luar biasa.
guy mograbi

265

Jika Anda perlu tahu properti mana yang berbeda, gunakan kurangi () :

_.reduce(a, function(result, value, key) {
    return _.isEqual(value, b[key]) ?
        result : result.concat(key);
}, []);
// → [ "prop2" ]

36
Perhatikan bahwa ini hanya akan menampilkan properti berbeda tingkat pertama. (Jadi itu tidak benar-benar jauh dalam arti mengeluarkan sifat-sifat yang berbeda.)
Bloke

16
Juga, ini tidak akan mengambil properti di b yang tidak dalam a.
Ed Staub

3
dan _.reduce(a, (result, value, key) => _.isEqual(value, b[key]) ? result : result.concat(key), [])untuk solusi satu baris ES6
Dotgreg

1
Versi yang menyimpulkan kunci: valuelet edited = _.reduce(a, function(result, value, key) { return _.isEqual(value, b[key]) ? result : result.concat( { [key]: value } ); }, []);
Aline Matos

47

Bagi siapa pun yang menemukan thread ini, inilah solusi yang lebih lengkap Ini akan membandingkan dua objek dan memberi Anda kunci dari semua properti yang hanya di object1 , hanya di object2 , atau keduanya di object1 dan object2 tetapi memiliki nilai yang berbeda :

/*
 * Compare two objects by reducing an array of keys in obj1, having the
 * keys in obj2 as the intial value of the result. Key points:
 *
 * - All keys of obj2 are initially in the result.
 *
 * - If the loop finds a key (from obj1, remember) not in obj2, it adds
 *   it to the result.
 *
 * - If the loop finds a key that are both in obj1 and obj2, it compares
 *   the value. If it's the same value, the key is removed from the result.
 */
function getObjectDiff(obj1, obj2) {
    const diff = Object.keys(obj1).reduce((result, key) => {
        if (!obj2.hasOwnProperty(key)) {
            result.push(key);
        } else if (_.isEqual(obj1[key], obj2[key])) {
            const resultKeyIndex = result.indexOf(key);
            result.splice(resultKeyIndex, 1);
        }
        return result;
    }, Object.keys(obj2));

    return diff;
}

Berikut ini contoh output:

// Test
let obj1 = {
    a: 1,
    b: 2,
    c: { foo: 1, bar: 2},
    d: { baz: 1, bat: 2 }
}

let obj2 = {
    b: 2, 
    c: { foo: 1, bar: 'monkey'}, 
    d: { baz: 1, bat: 2 }
    e: 1
}
getObjectDiff(obj1, obj2)
// ["c", "e", "a"]

Jika Anda tidak peduli dengan objek bersarang dan ingin melewatkan lodash, Anda dapat mengganti _.isEqualuntuk perbandingan nilai normal, misalnya obj1[key] === obj2[key].


Jawaban yang dipilih ini benar untuk hanya menguji kesetaraan. Jika Anda perlu tahu apa perbedaannya, tidak ada cara yang jelas untuk membuat daftar, tetapi jawaban ini cukup bagus, hanya memberikan daftar kunci properti tingkat atas di mana ada perbedaan. (Dan itu memberikan jawaban sebagai fungsi, yang membuatnya dapat digunakan.)
Sigfried

Apa perbedaan antara melakukan ini dan hanya menggunakan _.isEqual (obj1, obj2)? Apa yang menambahkan cek untuk hasOwnProperty melakukan hal yang _.isEqual tidak? Saya berada di bawah asumsi bahwa jika obj1 memiliki properti yang tidak dimiliki obj2, _.adalah yang tidak akan kembali benar ..?
Jaked222

2
@ Jaked222 - perbedaannya adalah isEqual mengembalikan boolean yang memberi tahu Anda apakah objeknya sama atau tidak, sedangkan fungsi di atas memberi tahu Anda apa yang berbeda antara kedua objek (jika mereka berbeda). Jika Anda hanya tertarik untuk mengetahui apakah dua objek itu sama, isEqual sudah cukup. Namun dalam banyak kasus, Anda ingin tahu apa perbedaan antara dua objek. Contohnya adalah jika Anda ingin mendeteksi perubahan sebelum dan sesudah sesuatu dan kemudian mengirimkan suatu peristiwa berdasarkan perubahan tersebut.
Johan Persson

30

Berdasarkan jawaban Adam Boduch , saya menulis fungsi ini yang membandingkan dua objek dalam arti yang paling dalam , mengembalikan jalur yang memiliki nilai berbeda serta jalur yang hilang dari satu atau objek lain.

Kode ini tidak ditulis dengan efisiensi dalam pikiran, dan perbaikan dalam hal itu disambut baik, tetapi di sini adalah bentuk dasar:

var compare = function (a, b) {

  var result = {
    different: [],
    missing_from_first: [],
    missing_from_second: []
  };

  _.reduce(a, function (result, value, key) {
    if (b.hasOwnProperty(key)) {
      if (_.isEqual(value, b[key])) {
        return result;
      } else {
        if (typeof (a[key]) != typeof ({}) || typeof (b[key]) != typeof ({})) {
          //dead end.
          result.different.push(key);
          return result;
        } else {
          var deeper = compare(a[key], b[key]);
          result.different = result.different.concat(_.map(deeper.different, (sub_path) => {
            return key + "." + sub_path;
          }));

          result.missing_from_second = result.missing_from_second.concat(_.map(deeper.missing_from_second, (sub_path) => {
            return key + "." + sub_path;
          }));

          result.missing_from_first = result.missing_from_first.concat(_.map(deeper.missing_from_first, (sub_path) => {
            return key + "." + sub_path;
          }));
          return result;
        }
      }
    } else {
      result.missing_from_second.push(key);
      return result;
    }
  }, result);

  _.reduce(b, function (result, value, key) {
    if (a.hasOwnProperty(key)) {
      return result;
    } else {
      result.missing_from_first.push(key);
      return result;
    }
  }, result);

  return result;
}

Anda dapat mencoba kode menggunakan snippet ini (disarankan berjalan dalam mode halaman penuh):


4
Saya baru saja memperbaiki bug, tetapi untuk memberi tahu Anda, Anda harus memeriksa keberadaan kunci dalam suatu objek b menggunakan b.hasOwnProperty(key)ataukey in b , tidak dengan b[key] != undefined. Dengan versi lama yang digunakan b[key] != undefined, fungsi mengembalikan diff yang salah untuk objek yang berisi undefined, seperti pada compare({disabled: undefined}, {disabled: undefined}). Bahkan, versi lama juga punya masalah dengan null; Anda dapat menghindari masalah seperti itu dengan selalu menggunakan ===dan!== bukannya ==dan !=.
Rory O'Kane

23

Inilah solusi singkat:

_.differenceWith(a, b, _.isEqual);

7
Sepertinya tidak bekerja dengan benda untuk saya. Alih-alih mengembalikan array kosong.
tomhughes

2
Juga mendapatkan array kosong dengan Lodash 4.17.4
aristidesfl

@ Z.Khullah Jika berhasil dengan cara ini, itu tidak didokumentasikan.
Brendon

1
@ Brendon, @ THughes, @aristidesfl maaf, saya telah mencampur hal-hal, ia bekerja dengan array objek, tetapi tidak untuk perbandingan objek yang dalam. Ternyata, jika tidak ada parameter array, lodash hanya akan kembali [].
Z. Khullah

7

Untuk menunjukkan secara berulang bagaimana suatu objek berbeda dengan yang lain, Anda dapat menggunakan _.reduce dikombinasikan dengan _.isEqual dan _.isPlainObject . Dalam hal ini Anda dapat membandingkan bagaimana a berbeda dengan b atau bagaimana b berbeda dengan:

var a = {prop1: {prop1_1: 'text 1', prop1_2: 'text 2', prop1_3: [1, 2, 3]}, prop2: 2, prop3: 3};
var b = {prop1: {prop1_1: 'text 1', prop1_3: [1, 2]}, prop2: 2, prop3: 4};

var diff = function(obj1, obj2) {
  return _.reduce(obj1, function(result, value, key) {
    if (_.isPlainObject(value)) {
      result[key] = diff(value, obj2[key]);
    } else if (!_.isEqual(value, obj2[key])) {
      result[key] = value;
    }
    return result;
  }, {});
};

var res1 = diff(a, b);
var res2 = diff(b, a);
console.log(res1);
console.log(res2);
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js"></script>


7

_.isEqualMetode penggunaan sederhana , ini akan bekerja untuk semua ...

  • Catatan: Metode ini mendukung perbandingan array, buffer array, boolean, * objek tanggal, objek kesalahan, peta, angka, Objectobjek, regex, * set, string, simbol, dan array yang diketik. Objectobjek dibandingkan * oleh properti mereka sendiri, tidak diwariskan, enumerable. Fungsi dan simpul DOM * tidak didukung.

Jadi, jika Anda memiliki di bawah ini:

 const firstName = {name: "Alireza"};
 const otherName = {name: "Alireza"};

Jika Anda melakukannya: _.isEqual(firstName, otherName);,

itu akan mengembalikan true

Dan jika const fullName = {firstName: "Alireza", familyName: "Dezfoolian"};

Jika Anda melakukannya: _.isEqual(firstName, fullName);,

akan kembali salah


6

Kode ini mengembalikan objek dengan semua properti yang memiliki nilai berbeda dan juga nilai dari kedua objek. Berguna untuk mencatat perbedaannya.

var allkeys = _.union(_.keys(obj1), _.keys(obj2));
var difference = _.reduce(allkeys, function (result, key) {
  if ( !_.isEqual(obj1[key], obj2[key]) ) {
    result[key] = {obj1: obj1[key], obj2: obj2[key]}
  }
  return result;
}, {});

3

Tanpa menggunakan lodash / garis bawah, saya telah menulis kode ini dan berfungsi dengan baik bagi saya untuk perbandingan objek1 yang mendalam dengan objek2

function getObjectDiff(a, b) {
    var diffObj = {};
    if (Array.isArray(a)) {
        a.forEach(function(elem, index) {
            if (!Array.isArray(diffObj)) {
                diffObj = [];
            }
            diffObj[index] = getObjectDiff(elem, (b || [])[index]);
        });
    } else if (a != null && typeof a == 'object') {
        Object.keys(a).forEach(function(key) {
            if (Array.isArray(a[key])) {
                var arr = getObjectDiff(a[key], b[key]);
                if (!Array.isArray(arr)) {
                    arr = [];
                }
                arr.forEach(function(elem, index) {
                    if (!Array.isArray(diffObj[key])) {
                        diffObj[key] = [];
                    }
                    diffObj[key][index] = elem;
                });
            } else if (typeof a[key] == 'object') {
                diffObj[key] = getObjectDiff(a[key], b[key]);
            } else if (a[key] != (b || {})[key]) {
                diffObj[key] = a[key];
            } else if (a[key] == (b || {})[key]) {
                delete a[key];
            }
        });
    }
    Object.keys(diffObj).forEach(function(key) {
        if (typeof diffObj[key] == 'object' && JSON.stringify(diffObj[key]) == '{}') {
            delete diffObj[key];
        }
    });
    return diffObj;
}

3

Bandingkan secara mendalam menggunakan templat properti (bersarang) untuk memeriksa

function objetcsDeepEqualByTemplate(objectA, objectB, comparisonTemplate) {
  if (!objectA || !objectB) return false

  let areDifferent = false
  Object.keys(comparisonTemplate).some((key) => {
    if (typeof comparisonTemplate[key] === 'object') {
      areDifferent = !objetcsDeepEqualByTemplate(objectA[key], objectB[key], comparisonTemplate[key])
      return areDifferent
    } else if (comparisonTemplate[key] === true) {
      areDifferent = objectA[key] !== objectB[key]
      return areDifferent
    } else {
      return false
    }
  })

  return !areDifferent
}

const objA = { 
  a: 1,
  b: {
    a: 21,
    b: 22,
  },
  c: 3,
}

const objB = { 
  a: 1,
  b: {
    a: 21,
    b: 25,
  },
  c: true,
}

// template tells which props to compare
const comparisonTemplateA = {
  a: true,
  b: {
    a: true
  }
}
objetcsDeepEqualByTemplate(objA, objB, comparisonTemplateA)
// returns true

const comparisonTemplateB = {
  a: true,
  c: true
}
// returns false
objetcsDeepEqualByTemplate(objA, objB, comparisonTemplateB)

Ini akan berfungsi di konsol. Dukungan array dapat ditambahkan jika diperlukan


2

Saya mengambil kode Adam Boduch untuk menghasilkan perbedaan yang dalam - ini sama sekali belum diuji tetapi potongannya ada di sana:

function diff (obj1, obj2, path) {
    obj1 = obj1 || {};
    obj2 = obj2 || {};

    return _.reduce(obj1, function(result, value, key) {
        var p = path ? path + '.' + key : key;
        if (_.isObject(value)) {
            var d = diff(value, obj2[key], p);
            return d.length ? result.concat(d) : result;
        }
        return _.isEqual(value, obj2[key]) ? result : result.concat(p);
    }, []);
}

diff({ foo: 'lol', bar: { baz: true }}, {}) // returns ["foo", "bar.baz"]

1
Bekerja seperti mantra, hanya saja urutan obj1 dan obj2 penting. Misalnya: diff({}, { foo: 'lol', bar: { baz: true }}) // returns []
amangpt777

2

Seperti yang ditanyakan, inilah fungsi perbandingan objek rekursif. Dan sedikit lagi. Dengan asumsi bahwa penggunaan utama dari fungsi tersebut adalah inspeksi objek, saya punya sesuatu untuk dikatakan. Perbandingan mendalam yang lengkap adalah ide yang buruk ketika beberapa perbedaan tidak relevan. Sebagai contoh, perbandingan yang dalam pada pernyataan TDD membuat tes tidak rapuh. Untuk alasan itu, saya ingin memperkenalkan diff parsial yang jauh lebih berharga . Ini adalah analog rekursif dari kontribusi sebelumnya ke utas ini. Ini mengabaikan kunci yang tidak ada dalam a

var bdiff = (a, b) =>
    _.reduce(a, (res, val, key) =>
        res.concat((_.isPlainObject(val) || _.isArray(val)) && b
            ? bdiff(val, b[key]).map(x => key + '.' + x) 
            : (!b || val != b[key] ? [key] : [])),
        []);

BDiff memungkinkan pengecekan nilai yang diharapkan sambil mentoleransi properti lain, yang persis seperti yang Anda inginkan untuk inspeksi otomatis . Ini memungkinkan membangun semua jenis pernyataan tingkat lanjut. Sebagai contoh:

var diff = bdiff(expected, actual);
// all expected properties match
console.assert(diff.length == 0, "Objects differ", diff, expected, actual);
// controlled inequality
console.assert(diff.length < 3, "Too many differences", diff, expected, actual);

Kembali ke solusi lengkap. Membangun diff tradisional penuh dengan bdiff itu sepele:

function diff(a, b) {
    var u = bdiff(a, b), v = bdiff(b, a);
    return u.filter(x=>!v.includes(x)).map(x=>' < ' + x)
    .concat(u.filter(x=>v.includes(x)).map(x=>' | ' + x))
    .concat(v.filter(x=>!u.includes(x)).map(x=>' > ' + x));
};

Menjalankan fungsi di atas pada dua objek kompleks akan menghasilkan sesuatu yang mirip dengan ini:

 [
  " < components.0.components.1.components.1.isNew",
  " < components.0.cryptoKey",
  " | components.0.components.2.components.2.components.2.FFT.min",
  " | components.0.components.2.components.2.components.2.FFT.max",
  " > components.0.components.1.components.1.merkleTree",
  " > components.0.components.2.components.2.components.2.merkleTree",
  " > components.0.components.3.FFTResult"
 ]

Akhirnya, untuk melihat bagaimana perbedaan nilai, kita mungkin ingin langsung mengevaluasi () output diff. Untuk itu, kita memerlukan versi jelek dari bdiff yang menghasilkan jalur yang benar secara sintaksis:

// provides syntactically correct output
var bdiff = (a, b) =>
    _.reduce(a, (res, val, key) =>
        res.concat((_.isPlainObject(val) || _.isArray(val)) && b
            ? bdiff(val, b[key]).map(x => 
                key + (key.trim ? '':']') + (x.search(/^\d/)? '.':'[') + x)
            : (!b || val != b[key] ? [key + (key.trim ? '':']')] : [])),
        []);

// now we can eval output of the diff fuction that we left unchanged
diff(a, b).filter(x=>x[1] == '|').map(x=>[x].concat([a, b].map(y=>((z) =>eval('z.' + x.substr(3))).call(this, y)))));

Itu akan menghasilkan sesuatu yang mirip dengan ini:

[" | components[0].components[2].components[2].components[2].FFT.min", 0, 3]
[" | components[0].components[2].components[2].components[2].FFT.max", 100, 50]

Lisensi MIT;)


1

Melengkapi jawaban dari Adam Boduch, yang ini memperhitungkan perbedaan dalam sifat

const differenceOfKeys = (...objects) =>
  _.difference(...objects.map(obj => Object.keys(obj)));
const differenceObj = (a, b) => 
  _.reduce(a, (result, value, key) => (
    _.isEqual(value, b[key]) ? result : [...result, key]
  ), differenceOfKeys(b, a));

1

Jika Anda hanya perlu perbandingan kunci:

 _.reduce(a, function(result, value, key) {
     return b[key] === undefined ? key : []
  }, []);

0

Berikut adalah skrip sederhana dengan Lodash deep difference checker yang akan menghasilkan objek baru dengan hanya perbedaan antara objek lama dan objek baru.

Misalnya, jika kita punya:

const oldData = {a: 1, b: 2};
const newData = {a: 1, b: 3};

objek yang dihasilkan adalah:

const result: {b: 3};

Ini juga kompatibel dengan objek mendalam multi-level, untuk array mungkin perlu beberapa penyesuaian.

import * as _ from "lodash";

export const objectDeepDiff = (data: object | any, oldData: object | any) => {
  const record: any = {};
  Object.keys(data).forEach((key: string) => {
    // Checks that isn't an object and isn't equal
    if (!(typeof data[key] === "object" && _.isEqual(data[key], oldData[key]))) {
      record[key] = data[key];
    }
    // If is an object, and the object isn't equal
    if ((typeof data[key] === "object" && !_.isEqual(data[key], oldData[key]))) {
      record[key] = objectDeepDiff(data[key], oldData[key]);
    }
  });
  return record;
};

-1
var isEqual = function(f,s) {
  if (f === s) return true;

  if (Array.isArray(f)&&Array.isArray(s)) {
    return isEqual(f.sort(), s.sort());
  }
  if (_.isObject(f)) {
    return isEqual(f, s);
  }
  return _.isEqual(f, s);
};

Ini tidak valid. Anda tidak dapat membandingkan objek dengan ===langsung, { a: 20 } === { a: 20 }akan mengembalikan false, karena membandingkan prototipe. Cara yang lebih tepat untuk membandingkan objek adalah dengan membungkusnyaJSON.stringify()
Herrgott

jika (f === s) mengembalikan true; - hanya untuk rekursi. Ya a: 20} === {a: 20} akan kembali salah dan pergi ke kondisi berikutnya
Crusader

kenapa tidak saja _.isEqual(f, s)? :)
Herrgott

Ini akan menghasilkan loop rekursi yang tak terbatas karena jika fadalah sebuah objek dan Anda mendapatkan if (_.isObject(f))Anda hanya kembali melalui fungsi dan tekan titik itu lagi. Sama berlaku untukf (Array.isArray(f)&&Array.isArray(s))
rady

-2

ini berdasarkan @JLavoie , menggunakan lodash

let differences = function (newObj, oldObj) {
      return _.reduce(newObj, function (result, value, key) {
        if (!_.isEqual(value, oldObj[key])) {
          if (_.isArray(value)) {
            result[key] = []
            _.forEach(value, function (innerObjFrom1, index) {
              if (_.isNil(oldObj[key][index])) {
                result[key].push(innerObjFrom1)
              } else {
                let changes = differences(innerObjFrom1, oldObj[key][index])
                if (!_.isEmpty(changes)) {
                  result[key].push(changes)
                }
              }
            })
          } else if (_.isObject(value)) {
            result[key] = differences(value, oldObj[key])
          } else {
            result[key] = value
          }
        }
        return result
      }, {})
    }

https://jsfiddle.net/EmilianoBarboza/0g0sn3b9/8/


-2

hanya menggunakan vanilla js

let a = {};
let b = {};

a.prop1 = 2;
a.prop2 = { prop3: 2 };

b.prop1 = 2;
b.prop2 = { prop3: 3 };

JSON.stringify(a) === JSON.stringify(b);
// false
b.prop2 = { prop3: 2};

JSON.stringify(a) === JSON.stringify(b);
// true

masukkan deskripsi gambar di sini


1
Metode ini tidak akan memberi tahu Anda atribut mana yang berbeda.
JLavoie

2
Dalam hal ini, atribut memesan pengaruh dalam hasilnya.
Victor Oliveira

-2

Untuk membangun berdasarkan jawaban Sridhar Gudimela , ini dia diperbarui dengan cara yang akan membuat Flow bahagia:

"use strict"; /* @flow */



//  E X P O R T

export const objectCompare = (objectA: any, objectB: any) => {
  let diffObj = {};

  switch(true) {
    case (Array.isArray(objectA)):
      objectA.forEach((elem, index) => {
        if (!Array.isArray(diffObj))
          diffObj = [];

        diffObj[index] = objectCompare(elem, (objectB || [])[index]);
      });

      break;

    case (objectA !== null && typeof objectA === "object"):
      Object.keys(objectA).forEach((key: any) => {
        if (Array.isArray(objectA[key])) {
          let arr = objectCompare(objectA[key], objectB[key]);

          if (!Array.isArray(arr))
            arr = [];

          arr.forEach((elem, index) => {
            if (!Array.isArray(diffObj[key]))
              diffObj[key] = [];

            diffObj[key][index] = elem;
          });
        } else if (typeof objectA[key] === "object")
          diffObj[key] = objectCompare(objectA[key], objectB[key]);
        else if (objectA[key] !== (objectB || {})[key])
          diffObj[key] = objectA[key];
        else if (objectA[key] === (objectB || {})[key])
          delete objectA[key];
      });

      break;

    default:
      break;
  }

  Object.keys(diffObj).forEach((key: any) => {
    if (typeof diffObj[key] === "object" && JSON.stringify(diffObj[key]) === "{}")
      delete diffObj[key];
  });

  return diffObj;
};
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.