İki nesne arasında genel derin fark


222

İki nesnem var: oldObjve newObj.

İçindeki veriler oldObjbir formu doldurmak için kullanıldı newObjve kullanıcının bu formdaki verileri değiştirip göndermesinin sonucudur.

Her iki nesne de derin, yani. nesneler veya nesnelerin dizisi gibi özelliklere sahiptirler - n seviyesi derin olabilirler, bu nedenle fark algoritmasının özyinelemeli olması gerekir.

Şimdi sadece dan (ilave olarak / güncellenen / silinmiş) değiştirildi anlamaya değil gerek oldObjiçin newObjdeğil, aynı zamanda en iyi şekilde bunu temsil nasıl.

Şimdiye kadar düşüncelerim sadece genericDeepDiffBetweenObjectsformdaki bir nesneyi döndürecek bir yöntem oluşturmaktı, {add:{...},upd:{...},del:{...}}ama sonra düşündüm: daha önce başka birine ihtiyaç duymuş olmalı.

Yani ... Herkes bunu yapacak bir kütüphane veya bir kod parçası biliyor ve belki de farkı temsil etmek için daha iyi bir yolu var (hala JSON serileştirilebilir bir şekilde)?

Güncelleme:

Güncellenmiş verileri temsil etmek için daha iyi bir yol, aynı nesne yapısını kullanarak newObj, ancak tüm özellik değerleri formdaki nesnelere çevirerek düşündüm :

{type: '<update|create|delete>', data: <propertyValue>}

Yani eğer newObj.prop1 = 'new value've oldObj.prop1 = 'old value'o kuracakreturnObj.prop1 = {type: 'update', data: 'new value'}

Güncelleme 2:

Dizi [1,2,3]eşit özelliklere sahip olduğumuzda gerçekten kıllı olur, çünkü dizi eşit olarak sayılmalıdır [2,3,1], dize, int & bool gibi değer tabanlı türlerden oluşan diziler için yeterince basittir, ancak söz konusu olduğunda işlenmesi gerçekten zorlaşır nesneler ve diziler gibi referans türlerinin dizileri.

Eşit bulunması gereken örnek diziler:

[1,[{c: 1},2,3],{a:'hey'}] and [{a:'hey'},1,[3,{c: 1},2]]

Sadece bu tip derin değer eşitliğini kontrol etmek değil, aynı zamanda olabilecek değişiklikleri temsil etmek için iyi bir yol bulmak da oldukça karmaşıktır.



2
@ a'r: stackoverflow.com/questions/1200562/… 'in kopyası değil - Nesneleri nasıl geçeceğimi biliyorum, bu önemsiz olmadığı ve uygulanması için gerçek zaman alacağı için önceki sanatı arıyorum ve ben kütüphaneyi sıfırdan yapmak yerine kullanmayı tercih ederim.
Martin Jespersen

1
Nesnelerin farkına gerçekten ihtiyacınız var mı, form gönderme yanıtında sunucudan oluşturulan newObj mi? Çünkü bir nesnenin "sunucu güncellemelerine" sahip değilseniz, uygun olay dinleyicilerini ekleyerek sorununuzu basitleştirebilirsiniz ve kullanıcı etkileşimi (nesne değişimi) üzerine istediğiniz değişiklik listesini güncelleyebilir / oluşturabilirsiniz.
sbgoran

1
@sbgoran: DOM'daki newObjbir formdaki js kodu okuma değerleri tarafından oluşturulur. Durumu korumanın ve bunu çok daha kolay hale getirmenin birkaç yolu var, ama bunu bir egzersiz olarak vatansız tutmak istiyorum. Ayrıca, eğer gerçekten kimse varsa, başkalarının bununla nasıl başa çıkabileceğini görmek için önceki sanatı arıyorum.
Martin Jespersen

3
İşte github.com/benjamine/jsondiffpatch herhangi bir çift Javascript nesnesini dif / yama için çok gelişmiş bir kütüphane burada canlı görebilirsiniz: benjamine.github.io/jsondiffpatch/demo/index.html (sorumluluk reddi: Ben yazarım)
Benja

Yanıtlar:


142

İstediğinizi yapan küçük bir sınıf yazdım, burada test edebilirsiniz .

Teklifinizden farklı olan tek şey, [1,[{c: 1},2,3],{a:'hey'}] and [{a:'hey'},1,[3,{c: 1},2]]aynı olduğunu düşünmememdir, çünkü öğelerinin sırası aynı değilse dizilerin eşit olmadığını düşünüyorum. Tabii ki bu gerekirse değiştirilebilir. Ayrıca bu kod, dif nesnesini geçirilen ilkel değerlere göre keyfi bir şekilde biçimlendirmek için kullanılacak argüman olarak işlev almak için daha da geliştirilebilir (şimdi bu iş "comparValues" yöntemi ile yapılıyor).

var deepDiffMapper = function () {
  return {
    VALUE_CREATED: 'created',
    VALUE_UPDATED: 'updated',
    VALUE_DELETED: 'deleted',
    VALUE_UNCHANGED: 'unchanged',
    map: function(obj1, obj2) {
      if (this.isFunction(obj1) || this.isFunction(obj2)) {
        throw 'Invalid argument. Function given, object expected.';
      }
      if (this.isValue(obj1) || this.isValue(obj2)) {
        return {
          type: this.compareValues(obj1, obj2),
          data: obj1 === undefined ? obj2 : obj1
        };
      }

      var diff = {};
      for (var key in obj1) {
        if (this.isFunction(obj1[key])) {
          continue;
        }

        var value2 = undefined;
        if (obj2[key] !== undefined) {
          value2 = obj2[key];
        }

        diff[key] = this.map(obj1[key], value2);
      }
      for (var key in obj2) {
        if (this.isFunction(obj2[key]) || diff[key] !== undefined) {
          continue;
        }

        diff[key] = this.map(undefined, obj2[key]);
      }

      return diff;

    },
    compareValues: function (value1, value2) {
      if (value1 === value2) {
        return this.VALUE_UNCHANGED;
      }
      if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) {
        return this.VALUE_UNCHANGED;
      }
      if (value1 === undefined) {
        return this.VALUE_CREATED;
      }
      if (value2 === undefined) {
        return this.VALUE_DELETED;
      }
      return this.VALUE_UPDATED;
    },
    isFunction: function (x) {
      return Object.prototype.toString.call(x) === '[object Function]';
    },
    isArray: function (x) {
      return Object.prototype.toString.call(x) === '[object Array]';
    },
    isDate: function (x) {
      return Object.prototype.toString.call(x) === '[object Date]';
    },
    isObject: function (x) {
      return Object.prototype.toString.call(x) === '[object Object]';
    },
    isValue: function (x) {
      return !this.isObject(x) && !this.isArray(x);
    }
  }
}();


var result = deepDiffMapper.map({
  a: 'i am unchanged',
  b: 'i am deleted',
  e: {
    a: 1,
    b: false,
    c: null
  },
  f: [1, {
    a: 'same',
    b: [{
      a: 'same'
    }, {
      d: 'delete'
    }]
  }],
  g: new Date('2017.11.25')
}, {
  a: 'i am unchanged',
  c: 'i am created',
  e: {
    a: '1',
    b: '',
    d: 'created'
  },
  f: [{
    a: 'same',
    b: [{
      a: 'same'
    }, {
      c: 'create'
    }]
  }, 1],
  g: new Date('2017.11.25')
});
console.log(result);


3
+1 Kötü bir kod parçası değil. Ancak bir hata var (bu örneği kontrol edin: jsfiddle.net/kySNu/3 c olarak oluşturulur, undefinedancak dize olmalıdır 'i am created') ve bunun yanı sıra, derin dizi değeri karşılaştırması eksik olduğundan ihtiyacım olanı yapmaz. en önemli (ve karmaşık / zor) kısım. Yan not olarak, 'array' != typeof(obj)diziler dizilerin örneği olan nesneler olduğu için yapı işe yaramaz.
Martin Jespersen

1
Kodu güncelledim, ancak sonuçta ortaya çıkan nesnede hangi değeri istediğinizden emin değilim, şu anda kod ilk nesneden değer döndürüyor ve eğer ikinci değerden değer yoksa veri olarak ayarlanacak.
sbgoran

1
Ve bu {type: ..., data:..}nesne için her dizin için alacağınız diziler için "derin dizi değeri karşılaştırmasının eksik olması" ne demek ? Eksik olan, ikinci dizideki değeri aramaktır, ancak cevabımda belirttiğim gibi, değerlerinin sırası (bence) eşit değilse dizilerin eşit olduğunu düşünmüyorum [1, 2, 3] is not equal to [3, 2, 1].
sbgoran

6
@MartinJespersen Tamam, nasıl genel olarak o zaman bu diziler görür: [{key: 'value1'}] and [{key: 'value2'}, {key: 'value3'}]. Şimdi ilk dizideki ilk nesne "value1" veya "value2" ile güncellendi. Ve bu basit bir örnek, derin yuvalama ile çok karmaşık olabilir. İsterseniz / nesnelerin diziler oluşturmak önceki örneğin gibi iç içe nesnelerle oluşturmaz bakılmaksızın kilit pozisyonu derin yuvalama karşılaştırılması gerekir: {inner: {key: 'value1'}} and {inner: {key: 'value2'}, otherInner: {key: 'value3'}}.
sbgoran

2
Size son bakış açısından katılıyorum - orijinal veri yapısı, gerçek bir farkın yapılması daha kolay bir şeyle değiştirilmelidir. Tebrikler,
çivirdin

88

Alt çizgi kullanarak basit bir fark:

var o1 = {a: 1, b: 2, c: 2},
    o2 = {a: 2, b: 1, c: 2};

_.omit(o1, function(v,k) { return o2[k] === v; })

Bunun bölümlerindeki sonuçlar, aşağıdaki o1değerlerde farklı değerlerle karşılık gelir o2:

{a: 1, b: 2}

Derin bir fark için farklı olurdu:

function diff(a,b) {
    var r = {};
    _.each(a, function(v,k) {
        if(b[k] === v) return;
        // but what if it returns an empty object? still attach?
        r[k] = _.isObject(v)
                ? _.diff(v, b[k])
                : v
            ;
        });
    return r;
}

@Juhana tarafından yorumlarda belirtildiği gibi, yukarıdaki sadece bir fark a -> b'dir ve geri dönüşümlü değildir (b'deki ekstra özellikler göz ardı edilir). Bunun yerine a -> b -> a kullanın:

(function(_) {
  function deepDiff(a, b, r) {
    _.each(a, function(v, k) {
      // already checked this or equal...
      if (r.hasOwnProperty(k) || b[k] === v) return;
      // but what if it returns an empty object? still attach?
      r[k] = _.isObject(v) ? _.diff(v, b[k]) : v;
    });
  }

  /* the function */
  _.mixin({
    diff: function(a, b) {
      var r = {};
      deepDiff(a, b, r);
      deepDiff(b, a, r);
      return r;
    }
  });
})(_.noConflict());

Tüm örnek + testler + karışımlar için http://jsfiddle.net/drzaus/9g5qoxwj/ adresine bakın.


Neden reddedildiğinizden emin değilsiniz, sığ, basit bir örnek ve daha karmaşık bir derin işlev sağladığınız için bu yeterliydi.
Seiyria

2
@ Seiyria nefret edenler nefret edecekler, sanırım ... Her ikisini de yaptım çünkü başlangıçta omitderin bir fark olacağını düşündüm , ama yanlıştı, bu yüzden karşılaştırma için de dahil edildi.
drzaus

1
Güzel çözüm. Ben değiştirmek öneririm r[k] = ... : vyılında r[k] = ... : {'a':v, 'b':b[k] }, iki değerlerini görebilirsiniz bu şekilde.
guyaloni

2
Objeler ile diğerleri özdeş olan, fakat ikinci bir kez daha elemanları, örneğin olduğunda Bunların her ikisi de, bir yanlış negatif geri {a:1, b:2}ve {a:1, b:2, c:3}.
JJJ

1
Bunun _.omitByyerine olmalı _.omit.
JP

48

Bir ES6 çözümü sunmak istiyorum ... Bu tek yönlü bir fark, yani anahtarları / değerleri o2, benzerleri ile aynı olmayan döndürecek anlamına gelir o1:

let o1 = {
  one: 1,
  two: 2,
  three: 3
}

let o2 = {
  two: 2,
  three: 3,
  four: 4
}

let diff = Object.keys(o2).reduce((diff, key) => {
  if (o1[key] === o2[key]) return diff
  return {
    ...diff,
    [key]: o2[key]
  }
}, {})

3
Güzel bir çözüm ama bu if(o1[key] === o1[key])çizgi dostum kontrol etmek isteyebilirsiniz
bm_i

Kod tamamlandı mı? Ben alıyorumUncaught SyntaxError: Unexpected token ...
Seano

2
Çözümü seviyorum ama bir sorunu var, eğer nesne bir seviyeden daha derinde ise, değiştirilmiş iç içe nesnelerdeki tüm değerleri döndürecektir - ya da en azından benim için olan şey.
Sahte

3
Evet, bu özyinelemeli @Spurious
Nemesarial

2
Bu çözümle, nesnedeki her öğe için, diziye bir öğe eklemek için içine kopyalanmış mevcut tüm öğelerle oluşturulmuş tamamen yeni bir nesne elde edeceğinizi unutmayın. Küçük nesneler için iyidir, ancak büyük nesneler için katlanarak yavaşlayacaktır.
Malvineous

22

Lodash kullanma:

_.mergeWith(oldObj, newObj, function (objectValue, sourceValue, key, object, source) {
    if ( !(_.isEqual(objectValue, sourceValue)) && (Object(objectValue) !== objectValue)) {
        console.log(key + "\n    Expected: " + sourceValue + "\n    Actual: " + objectValue);
    }
});

Anahtar / nesne / kaynak kullanmıyorum ama bunlara erişmeniz gerekiyorsa orada bıraktım. Nesne karşılaştırması, konsolun en dıştaki öğeden en içteki öğeye kadar konsoldaki farklılıkları yazdırmasını engeller.

Dizileri işlemek için içeride bir mantık ekleyebilirsiniz. Belki de önce dizileri sıralayın. Bu çok esnek bir çözüm.

DÜZENLE

Lodash güncellemesi nedeniyle _.merge yerine _.mergeWith olarak değiştirildi. Değişikliği fark ettiğiniz için Aviron teşekkürler.


6
Lodash 4.15.0'da _ özelleştirici işleviyle birleştirme artık desteklenmediği için _.mergeWith kullanmalısınız.
Aviran Cohen

1
bu işlev harika fakat iç içe nesnede çalışmıyor.
Joe Allen

13

İki JavaScript nesnesi arasındaki farkı bulmak için kullanabileceğiniz bir JavaScript kitaplığı:

Github URL'si: https://github.com/cosmicanant/recursive-diff

Npmjs url: https://www.npmjs.com/package/recursive-diff

Özyinelemeli-diff kütüphanesini tarayıcıda ve Node.js'de kullanabilirsiniz. Tarayıcı için aşağıdakileri yapın:

<script type="text" src="https://unpkg.com/recursive-diff@1.0.0/dist/recursive-diff.min.js"/>
<script type="text/javascript">
     const ob1 = {a:1, b: [2,3]};
     const ob2 = {a:2, b: [3,3,1]};
     const delta = recursiveDiff.getDiff(ob1,ob2); 
     /* console.log(delta) will dump following data 
     [
         {path: ['a'], op: 'update', val: 2}
         {path: ['b', '0'], op: 'update',val: 3},
         {path: ['b',2], op: 'add', val: 1 },
     ]
      */
     const ob3 = recursiveDiff.applyDiff(ob1, delta); //expect ob3 is deep equal to ob2
 </script>

Ancak node.js'de 'özyinelemeli-diff' modülüne ihtiyaç duyabilir ve aşağıdaki gibi kullanabilirsiniz:

const diff = require('recursive-diff');
const ob1 = {a: 1}, ob2: {b:2};
const diff = diff.getDiff(ob1, ob2);

Bu, örneğin Tarih özelliklerindeki değişiklikleri hesaba katmaz.
trollkotze

tarih desteği eklendi
Anant

9

Bugünlerde bunun için birkaç modül mevcut. Son zamanlarda bunu yapmak için bir modül yazdım, çünkü bulduğum çok sayıda farklı modülden memnun kalmadım. Adı odiff: https://github.com/Tixit/odiff . Ayrıca en popüler modülleri ve neden benioku içinde kabul edilemediğini listeledim odiff, hangi özelliklere sahip değilseniz bir göz atabilirsiniz odiff. İşte bir örnek:

var a = [{a:1,b:2,c:3},              {x:1,y: 2, z:3},              {w:9,q:8,r:7}]
var b = [{a:1,b:2,c:3},{t:4,y:5,u:6},{x:1,y:'3',z:3},{t:9,y:9,u:9},{w:9,q:8,r:7}]

var diffs = odiff(a,b)

/* diffs now contains:
[{type: 'add', path:[], index: 2, vals: [{t:9,y:9,u:9}]},
 {type: 'set', path:[1,'y'], val: '3'},
 {type: 'add', path:[], index: 1, vals: [{t:4,y:5,u:6}]}
]
*/

7
const diff = require("deep-object-diff").diff;
let differences = diff(obj2, obj1);

Haftada 500.000'den fazla indirilmiş npm modülü var: https://www.npmjs.com/package/deep-object-diff

Nesneyi farklılıkların temsili gibi seviyorum - özellikle yapıldığında, yapıyı görmek kolaydır.

const diff = require("deep-object-diff").diff;

const lhs = {
  foo: {
    bar: {
      a: ['a', 'b'],
      b: 2,
      c: ['x', 'y'],
      e: 100 // deleted
    }
  },
  buzz: 'world'
};

const rhs = {
  foo: {
    bar: {
      a: ['a'], // index 1 ('b')  deleted
      b: 2, // unchanged
      c: ['x', 'y', 'z'], // 'z' added
      d: 'Hello, world!' // added
    }
  },
  buzz: 'fizz' // updated
};

console.log(diff(lhs, rhs)); // =>
/*
{
  foo: {
    bar: {
      a: {
        '1': undefined
      },
      c: {
        '2': 'z'
      },
      d: 'Hello, world!',
      e: undefined
    }
  },
  buzz: 'fizz'
}
*/

2

Açıkladığınız görevi yapmak için bu kod parçasını kullandım:

function mergeRecursive(obj1, obj2) {
    for (var p in obj2) {
        try {
            if(obj2[p].constructor == Object) {
                obj1[p] = mergeRecursive(obj1[p], obj2[p]);
            }
            // Property in destination object set; update its value.
            else if (Ext.isArray(obj2[p])) {
                // obj1[p] = [];
                if (obj2[p].length < 1) {
                    obj1[p] = obj2[p];
                }
                else {
                    obj1[p] = mergeRecursive(obj1[p], obj2[p]);
                }

            }else{
                obj1[p] = obj2[p];
            }
        } catch (e) {
            // Property in destination object not set; create it and set its value.
            obj1[p] = obj2[p];
        }
    }
    return obj1;
}

bu size eski nesne ile yeni nesne arasındaki tüm değişiklikleri formunuzdan birleştirecek yeni bir nesne getirir.


1
Burada Dahili framework kullanıyorum ama istediğiniz diğer her ne çerçeve değiştirin ve kullanabilirsiniz ...
aMember

Nesneleri birleştirmek önemsizdir ve $.extend(true,obj1,obj2)jQuery kullanmak kadar kolay yapılabilir . İhtiyacım olan bu değil. İki nesne arasındaki farka değil bunların kombinasyonuna ihtiyacım var.
Martin Jespersen


2

Javascript'te "comparValue ()" adlı işlevi geliştirdim. değerin aynı olup olmadığını döndürür. Ben bir Object döngü için CompareValue () çağırdı. diffParams'ta iki nesnenin farkını elde edebilirsiniz.

var diffParams = {};
var obj1 = {"a":"1", "b":"2", "c":[{"key":"3"}]},
    obj2 = {"a":"1", "b":"66", "c":[{"key":"55"}]};

for( var p in obj1 ){
  if ( !compareValue(obj1[p], obj2[p]) ){
    diffParams[p] = obj1[p];
  }
}

function compareValue(val1, val2){
  var isSame = true;
  for ( var p in val1 ) {

    if (typeof(val1[p]) === "object"){
      var objectValue1 = val1[p],
          objectValue2 = val2[p];
      for( var value in objectValue1 ){
        isSame = compareValue(objectValue1[value], objectValue2[value]);
        if( isSame === false ){
          return false;
        }
      }
    }else{
      if(val1 !== val2){
        isSame = false;
      }
    }
  }
  return isSame;
}
console.log(diffParams);


1

Partiye geç kaldığımı biliyorum, ama yukarıdaki cevapların yardımcı olmadığı benzer bir şeye ihtiyacım vardı.

Bir değişkendeki değişiklikleri tespit etmek için Angular'ın $ watch işlevini kullanıyordum. Sadece bir özelliğin değişken üzerinde değişip değişmediğini bilmekle kalmadım, aynı zamanda değişen özelliğin geçici, hesaplanmış bir alan olmadığından emin olmak istedim. Başka bir deyişle, bazı özellikleri görmezden gelmek istedim.

İşte kod: https://jsfiddle.net/rv01x6jo/

Bunu nasıl kullanacağınız aşağıda açıklanmıştır:

// To only return the difference
var difference = diff(newValue, oldValue);  

// To exclude certain properties
var difference = diff(newValue, oldValue, [newValue.prop1, newValue.prop2, newValue.prop3]);

Umarım bu birine yardımcı olur.


Lütfen kodu sadece bir keman değil, cevabınıza da ekleyin.
xpy

DefinProperty daha iyi performans ile bu sorunu çözecek gibi görünüyor, eğer doğru hatırlıyorum IE9 aşağı kadar çalışır.
Peter

Teşekkürler..!! Kodunuz cazibe gibi çalışıyor ve günümü kurtardı. 1250 satır json nesnesi var ve bana tam istediğim o / p verir.
Tejas Mehta

1

Ben sadece ramda kullanın, aynı sorunu çözmek için, yeni nesnede ne değiştiğini bilmek gerekir. İşte benim tasarımım.

const oldState = {id:'170',name:'Ivab',secondName:'Ivanov',weight:45};
const newState = {id:'170',name:'Ivanko',secondName:'Ivanov',age:29};

const keysObj1 = R.keys(newState)

const filterFunc = key => {
  const value = R.eqProps(key,oldState,newState)
  return {[key]:value}
}

const result = R.map(filterFunc, keysObj1)

sonuç, mülkün adı ve durumudur.

[{"id":true}, {"name":false}, {"secondName":true}, {"age":false}]

1

İşte @sbgoran kodunun daktilo versiyonu

export class deepDiffMapper {

  static VALUE_CREATED = 'created';
  static VALUE_UPDATED = 'updated';
  static VALUE_DELETED = 'deleted';
  static VALUE_UNCHANGED ='unchanged';

  protected isFunction(obj: object) {
    return {}.toString.apply(obj) === '[object Function]';
  };

  protected isArray(obj: object) {
      return {}.toString.apply(obj) === '[object Array]';
  };

  protected isObject(obj: object) {
      return {}.toString.apply(obj) === '[object Object]';
  };

  protected isDate(obj: object) {
      return {}.toString.apply(obj) === '[object Date]';
  };

  protected isValue(obj: object) {
      return !this.isObject(obj) && !this.isArray(obj);
  };

  protected compareValues (value1: any, value2: any) {
    if (value1 === value2) {
        return deepDiffMapper.VALUE_UNCHANGED;
    }
    if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) {
        return deepDiffMapper.VALUE_UNCHANGED;
    }
    if ('undefined' == typeof(value1)) {
        return deepDiffMapper.VALUE_CREATED;
    }
    if ('undefined' == typeof(value2)) {
        return deepDiffMapper.VALUE_DELETED;
    }

    return deepDiffMapper.VALUE_UPDATED;
  }

  public map(obj1: object, obj2: object) {
      if (this.isFunction(obj1) || this.isFunction(obj2)) {
          throw 'Invalid argument. Function given, object expected.';
      }
      if (this.isValue(obj1) || this.isValue(obj2)) {
          return {
              type: this.compareValues(obj1, obj2),
              data: (obj1 === undefined) ? obj2 : obj1
          };
      }

      var diff = {};
      for (var key in obj1) {
          if (this.isFunction(obj1[key])) {
              continue;
          }

          var value2 = undefined;
          if ('undefined' != typeof(obj2[key])) {
              value2 = obj2[key];
          }

          diff[key] = this.map(obj1[key], value2);
      }
      for (var key in obj2) {
          if (this.isFunction(obj2[key]) || ('undefined' != typeof(diff[key]))) {
              continue;
          }

          diff[key] = this.map(undefined, obj2[key]);
      }

      return diff;

  }
}

1

İşte gisthub'da bulunan bir şeyin değiştirilmiş bir versiyonu .

isNullBlankOrUndefined = function (o) {
    return (typeof o === "undefined" || o == null || o === "");
}

/**
 * Deep diff between two object, using lodash
 * @param  {Object} object Object compared
 * @param  {Object} base   Object to compare with
 * @param  {Object} ignoreBlanks will not include properties whose value is null, undefined, etc.
 * @return {Object}        Return a new object who represent the diff
 */
objectDifference = function (object, base, ignoreBlanks = false) {
    if (!lodash.isObject(object) || lodash.isDate(object)) return object            // special case dates
    return lodash.transform(object, (result, value, key) => {
        if (!lodash.isEqual(value, base[key])) {
            if (ignoreBlanks && du.isNullBlankOrUndefined(value) && isNullBlankOrUndefined( base[key])) return;
            result[key] = lodash.isObject(value) && lodash.isObject(base[key]) ? objectDifference(value, base[key]) : value;
        }
    });
}

1

@ Sbgoran'ın cevabını değiştirdim, böylece ortaya çıkan diff nesnesi sadece değiştirilen değerleri içerecek ve aynı değerleri atlayacaktır . Ayrıca, hem orijinal değeri hem de güncellenmiş değeri gösterir .

var deepDiffMapper = function () {
    return {
        VALUE_CREATED: 'created',
        VALUE_UPDATED: 'updated',
        VALUE_DELETED: 'deleted',
        VALUE_UNCHANGED: '---',
        map: function (obj1, obj2) {
            if (this.isFunction(obj1) || this.isFunction(obj2)) {
                throw 'Invalid argument. Function given, object expected.';
            }
            if (this.isValue(obj1) || this.isValue(obj2)) {
                let returnObj = {
                    type: this.compareValues(obj1, obj2),
                    original: obj1,
                    updated: obj2,
                };
                if (returnObj.type != this.VALUE_UNCHANGED) {
                    return returnObj;
                }
                return undefined;
            }

            var diff = {};
            let foundKeys = {};
            for (var key in obj1) {
                if (this.isFunction(obj1[key])) {
                    continue;
                }

                var value2 = undefined;
                if (obj2[key] !== undefined) {
                    value2 = obj2[key];
                }

                let mapValue = this.map(obj1[key], value2);
                foundKeys[key] = true;
                if (mapValue) {
                    diff[key] = mapValue;
                }
            }
            for (var key in obj2) {
                if (this.isFunction(obj2[key]) || foundKeys[key] !== undefined) {
                    continue;
                }

                let mapValue = this.map(undefined, obj2[key]);
                if (mapValue) {
                    diff[key] = mapValue;
                }
            }

            //2020-06-13: object length code copied from https://stackoverflow.com/a/13190981/2336212
            if (Object.keys(diff).length > 0) {
                return diff;
            }
            return undefined;
        },
        compareValues: function (value1, value2) {
            if (value1 === value2) {
                return this.VALUE_UNCHANGED;
            }
            if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) {
                return this.VALUE_UNCHANGED;
            }
            if (value1 === undefined) {
                return this.VALUE_CREATED;
            }
            if (value2 === undefined) {
                return this.VALUE_DELETED;
            }
            return this.VALUE_UPDATED;
        },
        isFunction: function (x) {
            return Object.prototype.toString.call(x) === '[object Function]';
        },
        isArray: function (x) {
            return Object.prototype.toString.call(x) === '[object Array]';
        },
        isDate: function (x) {
            return Object.prototype.toString.call(x) === '[object Date]';
        },
        isObject: function (x) {
            return Object.prototype.toString.call(x) === '[object Object]';
        },
        isValue: function (x) {
            return !this.isObject(x) && !this.isArray(x);
        }
    }
}();

0

Projelerimden biri için, bir nesneyi kullanıcı seçenekleri olarak dahili klonuyla karşılaştıracak bir işlev zaten yazdım. Ayrıca, kullanıcı hatalı javascript'te kötü türde veri girdiyse veya kaldırıldıysa varsayılan değerlerle doğrulayabilir ve hatta bunları değiştirebilir.

IE8'de% 100 çalışıyor. Başarıyla test edildi.

//  ObjectKey: ["DataType, DefaultValue"]
reference = { 
    a : ["string", 'Defaul value for "a"'],
    b : ["number", 300],
    c : ["boolean", true],
    d : {
        da : ["boolean", true],
        db : ["string", 'Defaul value for "db"'],
        dc : {
            dca : ["number", 200],
            dcb : ["string", 'Default value for "dcb"'],
            dcc : ["number", 500],
            dcd : ["boolean", true]
      },
      dce : ["string", 'Default value for "dce"'],
    },
    e : ["number", 200],
    f : ["boolean", 0],
    g : ["", 'This is an internal extra parameter']
};

userOptions = { 
    a : 999, //Only string allowed
  //b : ["number", 400], //User missed this parameter
    c: "Hi", //Only lower case or case insitive in quotes true/false allowed.
    d : {
        da : false,
        db : "HelloWorld",
        dc : {
            dca : 10,
            dcb : "My String", //Space is not allowed for ID attr
            dcc: "3thString", //Should not start with numbers
            dcd : false
      },
      dce: "ANOTHER STRING",
    },
    e: 40,
    f: true,
};


function compare(ref, obj) {

    var validation = {
        number: function (defaultValue, userValue) {
          if(/^[0-9]+$/.test(userValue))
            return userValue;
          else return defaultValue;
        },
        string: function (defaultValue, userValue) {
          if(/^[a-z][a-z0-9-_.:]{1,51}[^-_.:]$/i.test(userValue)) //This Regex is validating HTML tag "ID" attributes
            return userValue;
          else return defaultValue;
        },
        boolean: function (defaultValue, userValue) {
          if (typeof userValue === 'boolean')
            return userValue;
          else return defaultValue;
        }
    };

    for (var key in ref)
        if (obj[key] && obj[key].constructor && obj[key].constructor === Object)
          ref[key] = compare(ref[key], obj[key]);
        else if(obj.hasOwnProperty(key))
          ref[key] = validation[ref[key][0]](ref[key][1], obj[key]); //or without validation on user enties => ref[key] = obj[key]
        else ref[key] = ref[key][1];
    return ref;
}

//console.log(
    alert(JSON.stringify( compare(reference, userOptions),null,2 ))
//);

/ * sonuç

{
  "a": "Defaul value for \"a\"",
  "b": 300,
  "c": true,
  "d": {
    "da": false,
    "db": "Defaul value for \"db\"",
    "dc": {
      "dca": 10,
      "dcb": "Default value for \"dcb\"",
      "dcc": 500,
      "dcd": false
    },
    "dce": "Default value for \"dce\""
  },
  "e": 40,
  "f": true,
  "g": "This is an internal extra parameter"
}

*/

0

Sbgoran'ın cevabından daha genişletilmiş ve basitleştirilmiş fonksiyon.
Bu, derin taramaya izin verir ve bir dizinin benzerliğini bulur.

var result = objectDifference({
      a:'i am unchanged',
      b:'i am deleted',
      e: {a: 1,b:false, c: null},
      f: [1,{a: 'same',b:[{a:'same'},{d: 'delete'}]}],
      g: new Date('2017.11.25'),
      h: [1,2,3,4,5]
  },
  {
      a:'i am unchanged',
      c:'i am created',
      e: {a: '1', b: '', d:'created'},
      f: [{a: 'same',b:[{a:'same'},{c: 'create'}]},1],
      g: new Date('2017.11.25'),
      h: [4,5,6,7,8]
  });
console.log(result);

function objectDifference(obj1, obj2){
    if((dataType(obj1) !== 'array' && dataType(obj1) !== 'object') || (dataType(obj2) !== 'array' && dataType(obj2) !== 'object')){
        var type = '';

        if(obj1 === obj2 || (dataType(obj1) === 'date' && dataType(obj2) === 'date' && obj1.getTime() === obj2.getTime()))
            type = 'unchanged';
        else if(dataType(obj1) === 'undefined')
            type = 'created';
        if(dataType(obj2) === 'undefined')
            type = 'deleted';
        else if(type === '') type = 'updated';

        return {
            type: type,
            data:(obj1 === undefined) ? obj2 : obj1
        };
    }
  
    if(dataType(obj1) === 'array' && dataType(obj2) === 'array'){
        var diff = [];
        obj1.sort(); obj2.sort();
        for(var i = 0; i < obj2.length; i++){
            var type = obj1.indexOf(obj2[i]) === -1?'created':'unchanged';
            if(type === 'created' && (dataType(obj2[i]) === 'array' || dataType(obj2[i]) === 'object')){
                diff.push(
                    objectDifference(obj1[i], obj2[i])
                );
                continue;
            }
            diff.push({
                type: type,
                data: obj2[i]
            });
        }

        for(var i = 0; i < obj1.length; i++){
            if(obj2.indexOf(obj1[i]) !== -1 || dataType(obj1[i]) === 'array' || dataType(obj1[i]) === 'object')
                continue;
            diff.push({
                type: 'deleted',
                data: obj1[i]
            });
        }
    } else {
        var diff = {};
        var key = Object.keys(obj1);
        for(var i = 0; i < key.length; i++){
            var value2 = undefined;
            if(dataType(obj2[key[i]]) !== 'undefined')
                value2 = obj2[key[i]];

            diff[key[i]] = objectDifference(obj1[key[i]], value2);
        }

        var key = Object.keys(obj2);
        for(var i = 0; i < key.length; i++){
            if(dataType(diff[key[i]]) !== 'undefined')
                continue;

            diff[key[i]] = objectDifference(undefined, obj2[key[i]]);
        }
    }

    return diff;
}

function dataType(data){
    if(data === undefined || data === null) return 'undefined';
    if(data.constructor === String) return 'string';
    if(data.constructor === Array) return 'array';
    if(data.constructor === Object) return 'object';
    if(data.constructor === Number) return 'number';
    if(data.constructor === Boolean) return 'boolean';
    if(data.constructor === Function) return 'function';
    if(data.constructor === Date) return 'date';
    if(data.constructor === RegExp) return 'regex';
    return 'unknown';
}


0

Burada iki nesne arasındaki farkı elde etmenin bir yolunu aramaya çalıştım. Bu benim Lodash kullanarak benim çözüm:

// Get updated values (including new values)
var updatedValuesIncl = _.omitBy(curr, (value, key) => _.isEqual(last[key], value));

// Get updated values (excluding new values)
var updatedValuesExcl = _.omitBy(curr, (value, key) => (!_.has(last, key) || _.isEqual(last[key], value)));

// Get old values (by using updated values)
var oldValues = Object.keys(updatedValuesIncl).reduce((acc, key) => { acc[key] = last[key]; return acc; }, {});

// Get newly added values
var newCreatedValues = _.omitBy(curr, (value, key) => _.has(last, key));

// Get removed values
var deletedValues = _.omitBy(last, (value, key) => _.has(curr, key));

// Then you can group them however you want with the result

Aşağıdaki kod snippet'i:

var last = {
"authed": true,
"inForeground": true,
"goodConnection": false,
"inExecutionMode": false,
"online": true,
"array": [1, 2, 3],
"deep": {
	"nested": "value",
},
"removed": "value",
};

var curr = {
"authed": true,
"inForeground": true,
"deep": {
	"nested": "changed",
},
"array": [1, 2, 4],
"goodConnection": true,
"inExecutionMode": false,
"online": false,
"new": "value"
};

// Get updated values (including new values)
var updatedValuesIncl = _.omitBy(curr, (value, key) => _.isEqual(last[key], value));
// Get updated values (excluding new values)
var updatedValuesExcl = _.omitBy(curr, (value, key) => (!_.has(last, key) || _.isEqual(last[key], value)));
// Get old values (by using updated values)
var oldValues = Object.keys(updatedValuesIncl).reduce((acc, key) => { acc[key] = last[key]; return acc; }, {});
// Get newly added values
var newCreatedValues = _.omitBy(curr, (value, key) => _.has(last, key));
// Get removed values
var deletedValues = _.omitBy(last, (value, key) => _.has(curr, key));

console.log('oldValues', JSON.stringify(oldValues));
console.log('updatedValuesIncl', JSON.stringify(updatedValuesIncl));
console.log('updatedValuesExcl', JSON.stringify(updatedValuesExcl));
console.log('newCreatedValues', JSON.stringify(newCreatedValues));
console.log('deletedValues', JSON.stringify(deletedValues));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>


0

Yukarıdaki cevabı @sbgoran tarafından aldım ve dizilerimi kümeler olarak ele almak için gereken soru ile aynı durumum için değiştirdim (yani, fark için sipariş önemli değil)

const deepDiffMapper = function () {
return {
  VALUE_CREATED: "created",
  VALUE_UPDATED: "updated",
  VALUE_DELETED: "deleted",
  VALUE_UNCHANGED: "unchanged",
  map: function(obj1: any, obj2: any) {
    if (this.isFunction(obj1) || this.isFunction(obj2)) {
      throw "Invalid argument. Function given, object expected.";
    }
    if (this.isValue(obj1) || this.isValue(obj2)) {
      return {
        type: this.compareValues(obj1, obj2),
        data: obj2 === undefined ? obj1 : obj2
      };
    }

    if (this.isArray(obj1) || this.isArray(obj2)) {
      return {
        type: this.compareArrays(obj1, obj2),
        data: this.getArrayDiffData(obj1, obj2)
      };
    }

    const diff: any = {};
    for (const key in obj1) {

      if (this.isFunction(obj1[key])) {
        continue;
      }

      let value2 = undefined;
      if (obj2[key] !== undefined) {
        value2 = obj2[key];
      }

      diff[key] = this.map(obj1[key], value2);
    }
    for (const key in obj2) {
      if (this.isFunction(obj2[key]) || diff[key] !== undefined) {
        continue;
      }

      diff[key] = this.map(undefined, obj2[key]);
    }

    return diff;

  },

  getArrayDiffData: function(arr1: Array<any>, arr2: Array<any>) {
    const set1 = new Set(arr1);
    const set2 = new Set(arr2);

    if (arr1 === undefined || arr2 === undefined) {
       return arr1 === undefined ? arr1 : arr2;
    }
    const deleted = [...arr1].filter(x => !set2.has(x));

    const added = [...arr2].filter(x => !set1.has(x));

    return {
      added, deleted
    };

  },

  compareArrays: function(arr1: Array<any>, arr2: Array<any>) {
    const set1 = new Set(arr1);
    const set2 = new Set(arr2);
    if (_.isEqual(_.sortBy(arr1), _.sortBy(arr2))) {
      return this.VALUE_UNCHANGED;
    }
    if (arr1 === undefined) {
      return this.VALUE_CREATED;
    }
    if (arr2 === undefined) {
      return this.VALUE_DELETED;
    }
    return this.VALUE_UPDATED;
  },
  compareValues: function (value1: any, value2: any) {
    if (value1 === value2) {
      return this.VALUE_UNCHANGED;
    }
    if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) {
      return this.VALUE_UNCHANGED;
    }
    if (value1 === undefined) {
      return this.VALUE_CREATED;
    }
    if (value2 === undefined) {
      return this.VALUE_DELETED;
    }
    return this.VALUE_UPDATED;
  },
  isFunction: function (x: any) {
    return Object.prototype.toString.call(x) === "[object Function]";
  },
  isArray: function (x: any) {
    return Object.prototype.toString.call(x) === "[object Array]";
  },
  isDate: function (x: any) {
    return Object.prototype.toString.call(x) === "[object Date]";
  },
  isObject: function (x: any) {
    return Object.prototype.toString.call(x) === "[object Object]";
  },
  isValue: function (x: any) {
    return !this.isObject(x) && !this.isArray(x);
  }
 };
}();

0

İşte bir çözüm:

  • Daktilo metni (ancak kolayca Javascript'e dönüştürülebilir)
  • lib bağımlılığı yok
  • geneldir ve nesne türlerini kontrol etmekle ilgilenmez (türün yanı sıra object)
  • değeri olan özellikleri destekler undefined
  • derin değil (varsayılan)

İlk olarak karşılaştırma sonucu arayüzünü tanımlarız:

export interface ObjectComparison {
  added: {};
  updated: {
    [propName: string]: Change;
  };
  removed: {};
  unchanged: {};
}

eski ve yeni değerlerin ne olduğunu bilmek istediğimiz özel değişim durumu ile:

export interface Change {
  oldValue: any;
  newValue: any;
}

Sonra sağlayabilir diff(eğer recursivity ile sadece iki halka olan işlevi deepolan true):

export class ObjectUtils {

  static diff(o1: {}, o2: {}, deep = false): ObjectComparison {
    const added = {};
    const updated = {};
    const removed = {};
    const unchanged = {};
    for (const prop in o1) {
      if (o1.hasOwnProperty(prop)) {
        const o2PropValue = o2[prop];
        const o1PropValue = o1[prop];
        if (o2.hasOwnProperty(prop)) {
          if (o2PropValue === o1PropValue) {
            unchanged[prop] = o1PropValue;
          } else {
            updated[prop] = deep && this.isObject(o1PropValue) && this.isObject(o2PropValue) ? this.diff(o1PropValue, o2PropValue, deep) : {newValue: o2PropValue};
          }
        } else {
          removed[prop] = o1PropValue;
        }
      }
    }
    for (const prop in o2) {
      if (o2.hasOwnProperty(prop)) {
        const o1PropValue = o1[prop];
        const o2PropValue = o2[prop];
        if (o1.hasOwnProperty(prop)) {
          if (o1PropValue !== o2PropValue) {
            if (!deep || !this.isObject(o1PropValue)) {
              updated[prop].oldValue = o1PropValue;
            }
          }
        } else {
          added[prop] = o2PropValue;
        }
      }
    }
    return { added, updated, removed, unchanged };
  }

  /**
   * @return if obj is an Object, including an Array.
   */
  static isObject(obj: any) {
    return obj !== null && typeof obj === 'object';
  }
}

Örnek olarak:

ObjectUtils.diff(
  {
    a: 'a', 
    b: 'b', 
    c: 'c', 
    arr: ['A', 'B'], 
    obj: {p1: 'p1', p2: 'p2'}
  },
  {
    b: 'x', 
    c: 'c', 
    arr: ['B', 'C'], 
    obj: {p2: 'p2', p3: 'p3'}, 
    d: 'd'
  },
);

dönecek:

{
  added: {d: 'd'},
  updated: {
    b: {oldValue: 'b', newValue: 'x'},
    arr: {oldValue: ['A', 'B'], newValue: ['B', 'C']},
    obj: {oldValue: {p1: 'p1', p2: 'p2'}, newValue: {p2: 'p2', p3: 'p3'}}
  },
  removed: {a: 'a'},
  unchanged: {c: 'c'},
}

ve aynı deepüçüncü parametre ile çağrıldığında :

{
  added: {d: 'd'},
  updated: {
    b: {oldValue: 'b', newValue: 'x'},
    arr: {
      added: {},
      removed: {},
      unchanged: {},
      updated: {
        0: {oldValue: 'A', newValue: 'B'},
        1: {oldValue: 'B', newValue: 'C', }
      }
    },
    obj: {
      added: {p3: 'p3'},
      removed: {p1: 'p1'},
      unchanged: {p2: 'p2'},
      updated: {}
    }
  },
  removed: {a: 'a'},
  unchanged: {c: 'c'},
}

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.