Lodash ile 2 nesne arasında derin bir karşılaştırma nasıl yapılır?


309

Farklı 2 iç içe nesneler var ve iç içe özelliklerinden birinde fark olup olmadığını bilmek gerekir.

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

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

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

Nesne, daha iç içe özelliklerle çok daha karmaşık olabilir. Ama bu iyi bir örnek. Ben özyinelemeli fonksiyonları veya lodash ile bir şey kullanma seçeneği var ...


Derin karşılaştırma için stackoverflow.com/a/46003894/696535
Pawel

7
_.isEqual(value, other)Eşdeğer olup olmadığını belirlemek için iki değer arasında derin bir karşılaştırma yapar. Eşit
Lukas Liesis

JSON.stringify ()
xgqfrms

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

Yanıtlar:


475

_.isEqualDerin bir karşılaştırma yapan kolay ve zarif bir çözüm kullanmaktır :

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

Ancak, bu çözüm hangi özelliğin farklı olduğunu göstermez.

http://jsfiddle.net/bdkeyn0h/


2
Cevabın oldukça eski olduğunu biliyorum, ama eklemek istiyorum, bu _.isEqualoldukça zor olabilir. Nesneyi kopyalar ve içindeki bazı değerleri değiştirirseniz, referans aynı olduğu için hala true değerini gösterir. Bu yüzden bu işlevi kullanırken dikkatli olunmalıdır.
oruckdeschel

23
@ oruckdeschel başvuru aynı ise, aynı nesnedir. dolayısıyla eşittir. Bu bir işaretçi kadar aldatıcı değil. lodash harika.
guy mograbi

265

Hangi özelliklerin farklı olduğunu bilmeniz gerekirse, reduce () kullanın :

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

36
Bunun yalnızca birinci düzey farklı özelliklerin çıkacağını unutmayın. (Bu nedenle , farklı özelliklerin çıktısını alma anlamında gerçekten derin değildir .)
Bloke

16
Ayrıca, bu, b'de a öğesinde bulunmayan özellikleri almaz.
Ed Staub

3
ve _.reduce(a, (result, value, key) => _.isEqual(value, b[key]) ? result : result.concat(key), [])tek satırlı bir ES6 çözümü için
Dotgreg

1
Anahtarı sonuçlandıran bir versiyon: değerlet edited = _.reduce(a, function(result, value, key) { return _.isEqual(value, b[key]) ? result : result.concat( { [key]: value } ); }, []);
Aline Matos

47

Bu konu hakkında tökezleyen herkes için, burada daha eksiksiz bir çözüm. Bu karşılaştırır iki nesneyi ve size ya tüm özelliklerinin anahtarı vermek sadece nesneye1 yılında , sadece Object2 içinde ya vardır nesneye1 ve Object2 hem ama farklı değerlere sahip :

/*
 * 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;
}

İşte bir örnek çıktı:

// 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"]

Yuvalanmış nesneler ile ilgilenmiyorsanız ve lodash'ı atlamak istiyorsanız, _.isEqualnormal değer karşılaştırmasını (ör obj1[key] === obj2[key].


Seçilen bu cevap sadece eşitliği test etmek için doğrudur. Farklılıkların ne olduğunu bilmeniz gerekiyorsa, bunları listelemenin açık bir yolu yoktur, ancak bu cevap oldukça iyidir, sadece bir farkın olduğu üst düzey özellik anahtarlarının bir listesini verir. (Ve cevabı bir işlev olarak verir, bu da kullanılabilir hale getirir.)
Sigfried

Bunu yapmakla sadece _.isEqual (obj1, obj2) kullanmak arasındaki fark nedir? _.İsEqual'in hasOwnProperty için çek eklenmesi ne yapmaz? Ben obj1 obj2 olmayan bir özellik olsaydı _ _isEqual doğru dönmeyecek varsayalım.
Jaked222

2
@ Jaked222 - fark şu ki isEqual, nesnelerin eşit olup olmadığını söyleyen bir boole döndürürken, yukarıdaki işlev size iki nesne arasında (farklıysa) farklı olanı söyler . Yalnızca iki nesnenin aynı olup olmadığını bilmekle ilgileniyorsanız, isEqual yeterlidir. Yine de birçok durumda, iki nesne arasındaki farkın ne olduğunu bilmek istersiniz. Bir şeyden önce ve sonra değişiklikleri tespit etmek ve daha sonra değişikliklere göre bir olay göndermek istiyorsanız buna bir örnek verilebilir.
Johan Persson

30

Adam Boduch'un cevabına dayanarak, iki nesneyi mümkün olan en derin anlamda karşılaştıran bu işlevi yazdım , farklı değerlere sahip yolları ve bir veya diğer nesneden eksik yolları döndüren .

Kod verimlilik göz önünde bulundurularak yazılmadı ve bu konudaki iyileştirmeler çok hoş karşılandı, ancak temel form:

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

Bu snippet'i kullanarak kodu deneyebilirsiniz (tam sayfa modunda çalışması önerilir):


4
Sadece hata düzeltildi, ancak bir nesne içinde anahtar varlığını kontrol etmelidir, bilmeni b kullanarak b.hasOwnProperty(key)veyakey in b değil birlikte b[key] != undefined. Kullanılan eski sürümde b[key] != undefined, işlev undefined, olduğu gibi, nesneler için yanlış bir fark döndürdü compare({disabled: undefined}, {disabled: undefined}). Aslında, eski sürümde de sorunlar vardı null; her zaman ===ve!== yerine ==ve kullanarak bu tür sorunları önleyebilirsiniz !=.
Rory O'Kane

23

İşte özlü bir çözüm:

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

7
Benim için nesnelerle çalışmıyor gibi görünüyor. Bunun yerine boş bir dizi döndürür.
tomhughes

2
Ayrıca Lodash 4.17.4 ile boş dizi elde
aristidesfl

@ Z.Khullah Bu şekilde çalışsaydı belgelenmez.
Brendon

1
@Brendon, @THughes, @aristidesfl üzgünüm, bir şeyler karıştırdım, nesnelerin dizileriyle çalışıyor, ancak derin nesne karşılaştırmaları için değil. Anlaşıldığı üzere, her iki parametre de diziyse, lodash geri dönecektir [].
Z. Khullah

7

Yinelemeli bir nesne kullanabileceğiniz diğer farklı olduğunu göstermek için _.reduce ile kombine _.isEqual ve _.isPlainObject . Bu durumda a'nın b ile nasıl farklı olduğunu veya b'nin a ile nasıl farklı olduğunu karşılaştırabilirsiniz:

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

Basit kullanım _.isEqualyöntemi, tüm karşılaştırma için çalışacaktır ...

  • Not: Bu yöntem, dizileri, dizi arabelleklerini, booleları, * tarih nesnelerini, hata nesnelerini, haritaları, sayıları, Objectnesneleri, normal ifadeleri, * kümeleri, dizeleri, simgeleri ve yazılan dizileri karşılaştırmayı destekler. Objectnesneler, kalıtsal olmayan, numaralandırılabilir özellikleri ile karşılaştırılır. Fonksiyonlar ve DOM * düğümleri vardır değil destekledi.

Yani aşağıda varsa:

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

Yaparsan: _.isEqual(firstName, otherName); ,

doğru dönecek

Ve eğer const fullName = {firstName: "Alireza", familyName: "Dezfoolian"};

Yaparsan: _.isEqual(firstName, fullName); ,

yanlış dönecek


6

Bu kod, farklı bir değere sahip tüm özelliklere ve her iki nesnenin değerlerine sahip bir nesne döndürür. Farkı kaydetmek için kullanışlıdır.

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

Lodash / alt çizgi kullanmadan, bu kodu yazdım ve object1 ile object2'nin derin bir karşılaştırması için benim için iyi çalışıyor

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

Kontrol etmek için (iç içe) özelliklerin bir şablonunu kullanarak derin karşılaştırma

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)

Bu konsolda çalışacaktır. Gerekirse dizi desteği eklenebilir


2

Derin bir fark yaratmak için Adam Boduch'un kodunu bıçakladım - bu tamamen test edilmedi ama parçalar var:

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
Bir cazibe gibi çalışır, sadece obj1 ve obj2 sıralaması önemlidir. Örneğin: diff({}, { foo: 'lol', bar: { baz: true }}) // returns []
amangpt777

2

Sorulduğu gibi, burada özyinelemeli nesne karşılaştırma işlevi. Ve biraz daha. Böyle bir işlevin birincil kullanımının nesne denetimi olduğunu varsayarsak, söyleyecek bir şeyim var. Bazı farklılıklar alakasız olduğunda tam derin karşılaştırma kötü bir fikirdir. Örneğin, TDD iddialarında kör derin karşılaştırma testleri gereksiz kırılgan hale getirir. Bu nedenle, çok daha değerli bir kısmi fark tanıtmak istiyorum . Bu konuya önceki bir katkının özyinelemeli bir analogudur. O bulunmayan anahtarları yok sayar 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] : [])),
        []);

Tam olarak ne için istediğini diğer özellikleri, tolere ederken BDiff beklenen değerler için kontrol sağlar otomatik muayene. Bu, her türlü gelişmiş iddiaların oluşturulmasına izin verir. Örneğin:

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

Komple çözüme dönülüyor. Bdiff ile tam bir geleneksel fark oluşturmak önemsizdir:

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

İki karmaşık nesnede yukarıdaki işlevin çalıştırılması buna benzer bir şey çıkarır:

 [
  " < 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"
 ]

Son olarak, değerlerin nasıl farklılaştığına bir göz atmak için , fark çıktısını doğrudan değerlendirmek () isteyebiliriz . Bunun için, sözdizimsel olarak doğru yolların çıktısını veren daha çirkin bir bdiff sürümüne ihtiyacımız var :

// 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)))));

Bu şuna benzer bir şey çıkarır:

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

MIT lisansı;)


1

Adam Boduch'un cevabını tamamlayan bu, mülklerdeki farklılıklara giriyor

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

Yalnızca anahtar karşılaştırmaya ihtiyacınız varsa:

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

0

İşte sadece eski bir nesne ile yeni bir nesne arasındaki farklarla yeni bir nesne üretecek Lodash derin fark denetleyicisine sahip basit bir dizgi.

Örneğin, eğer:

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

sonuçta elde edilen nesne:

const result: {b: 3};

Ayrıca çok seviyeli derin nesnelerle de uyumludur, diziler için bazı ayarlamalar gerekebilir.

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

Bu geçersiz. Nesneleri ===doğrudan { a: 20 } === { a: 20 }karşılaştıramazsınız, prototip karşılaştırdığı için false döndürür. Öncelikle nesneleri karşılaştırmanın daha doğru yolu, onları JSON.stringify()
sarmaktır

(f === s) true değerini döndürürse; - sadece özyineleme içindir. Evet a: 20} === {a: 20} yanlış dönecek ve bir sonraki koşula geçecektir
Haçlı

neden sadece _.isEqual(f, s)? :)
Herrgott

Bu, sonsuz bir özyineleme döngüsüne neden olur f, çünkü eğer bir nesne ise ve size ulaşırsanız if (_.isObject(f))fonksiyona geri dönüp o noktaya tekrar vurursunuz. Aynı giderf (Array.isArray(f)&&Array.isArray(s))
rady

-2

bu, lodash kullanarak @JLavoie'ye dayanıyordu

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

sadece vanilya js kullanarak

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

resim açıklamasını buraya girin


1
Bu yöntem size hangi özelliklerin farklı olduğunu söylemez.
JLavoie

2
Bu durumda, nitelikler sonuçta etkili olur.
Victor Oliveira

-2

Üzerine inşa etmek Sridhar Gudimela cevabı , işte Akış mutlu edecek şekilde güncellenir:

"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;
};
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.