web-dev-qa-db-ja.com

2つのJavaScriptオブジェクトグラフの違いのリストを取得するにはどうすればよいですか?

2つのJavaScriptオブジェクトグラフ間のすべての違いのリストを、デルタが発生するプロパティ名と値とともに取得できるようにしたいと思います。

これらのオブジェクトは通常、サーバーからJSONとして取得され、通常はほんの一握りの深さのレイヤーにすぎません(つまり、それ自体がデータを持ち、次に他のデータオブジェクトとの配列である可能性があります)。

基本的なプロパティの変更だけでなく、配列のメンバー数の違いなども見たいです。

答えが得られない場合は、おそらく自分でこれを書くことになりますが、誰かがすでにこの作業を行っているか、誰かを知っていることを願っています。


編集:これらのオブジェクトは通常、構造が互いに非常に近いため、互いにまったく異なるオブジェクトについては説明していませんが、3つまたは4つのデルタがある場合があります。

17
Jason Bunting

既存の回答を確認した後、 https://github.com/flitbit/diff ライブラリがまだ解決策としてリストされていないことに気付きました。

私の調査によると、このライブラリは、オブジェクトの差分の課題を解決するための積極的な開発、貢献、およびフォークの点で最高のようです。これは、サーバー側で差分を作成し、変更されたビットのみをクライアントに渡す場合に非常に便利です。

15
ryanm

これが私の問題に対する部分的でナイーブな解決策です-私はそれをさらに発展させながらこれを更新します。

function findDifferences(objectA, objectB) {
   var propertyChanges = [];
   var objectGraphPath = ["this"];
   (function(a, b) {
      if(a.constructor == Array) {
         // BIG assumptions here: That both arrays are same length, that
         // the members of those arrays are _essentially_ the same, and 
         // that those array members are in the same order...
         for(var i = 0; i < a.length; i++) {
            objectGraphPath.Push("[" + i.toString() + "]");
            arguments.callee(a[i], b[i]);
            objectGraphPath.pop();
         }
      } else if(a.constructor == Object || (a.constructor != Number && 
                a.constructor != String && a.constructor != Date && 
                a.constructor != RegExp && a.constructor != Function &&
                a.constructor != Boolean)) {
         // we can safely assume that the objects have the 
         // same property lists, else why compare them?
         for(var property in a) {
            objectGraphPath.Push(("." + property));
            if(a[property].constructor != Function) {
               arguments.callee(a[property], b[property]);
            }
            objectGraphPath.pop();
         }
      } else if(a.constructor != Function) { // filter out functions
         if(a != b) {
            propertyChanges.Push({ "Property": objectGraphPath.join(""), "ObjectA": a, "ObjectB": b });
         }
      }
   })(objectA, objectB);
   return propertyChanges;
}

そして、これがどのように使用されるか、そしてそれが提供するデータのサンプルです(長い例を許してください、しかし私は比較的重要なものを使いたいです):

var person1 = { 
   FirstName : "John", 
   LastName : "Doh", 
   Age : 30, 
   EMailAddresses : [
      "[email protected]", 
      "[email protected]"
   ], 
   Children : [ 
      { 
         FirstName : "Sara", 
         LastName : "Doe", 
         Age : 2 
      }, { 
         FirstName : "Beth", 
         LastName : "Doe", 
         Age : 5 
      } 
   ] 
};

var person2 = { 
   FirstName : "John", 
   LastName : "Doe", 
   Age : 33, 
   EMailAddresses : [
      "[email protected]", 
      "[email protected]"
   ], 
   Children : [ 
      { 
         FirstName : "Sara", 
         LastName : "Doe", 
         Age : 3 
      }, { 
         FirstName : "Bethany", 
         LastName : "Doe", 
         Age : 5 
      } 
   ] 
};

var differences = findDifferences(person1, person2);

この時点で、differences配列をJSONにシリアル化した場合は次のようになります。

[
   {
      "Property":"this.LastName", 
      "ObjectA":"Doh", 
      "ObjectB":"Doe"
   }, {
      "Property":"this.Age", 
      "ObjectA":30, 
      "ObjectB":33
   }, {
      "Property":"this.EMailAddresses[1]", 
      "ObjectA":"[email protected]", 
      "ObjectB":"[email protected]"
   }, {
      "Property":"this.Children[0].Age", 
      "ObjectA":2, 
      "ObjectB":3
   }, {
      "Property":"this.Children[1].FirstName", 
      "ObjectA":"Beth", 
      "ObjectB":"Bethany"
   }
]

this値のPropertyは、比較されたオブジェクトのルートを参照します。したがって、このソリューションはまだ正確に必要なものではありませんが、かなり近いです。

これが他の誰かに役立つことを願っています。改善のための提案があれば、私はすべての耳です。私はこれを昨夜遅く(つまり今朝早く)書いたのですが、完全に見落としていることがあるかもしれません。

ありがとう。

20
Jason Bunting

objectDiffライブラリ があり、これを使用できます。 デモページ で、2つのjavascriptオブジェクトの違いを確認できます。

8
anton_byrna

Rus-diff https://github.com/mirek/node-rus-diff を試すこともできます。これにより、MongoDB互換の(rename/unset/set)diffが生成されます。

サンプルオブジェクトの場合:

var person1 = {
  FirstName: "John",
  LastName: "Doh",
  Age: 30,
  EMailAddresses: ["[email protected]", "[email protected]"],
  Children: [
    {
      FirstName: "Sara",
      LastName: "Doe",
      Age: 2
    }, {
      FirstName: "Beth",
      LastName: "Doe",
      Age: 5
    }
  ]
};

var person2 = {
  FirstName: "John",
  LastName: "Doe",
  Age: 33,
  EMailAddresses: ["[email protected]", "[email protected]"],
  Children: [
    {
      FirstName: "Sara",
      LastName: "Doe",
      Age: 3
    }, {
      FirstName: "Bethany",
      LastName: "Doe",
      Age: 5
    }
  ]
};

var rusDiff = require('rus-diff').rusDiff

console.log(rusDiff(person1, person2))

セットのリストを生成します。

{ '$set': 
   { 'Age': 33,
     'Children.0.Age': 3,
     'Children.1.FirstName': 'Bethany',
     'EMailAddresses.1': '[email protected]',
     'LastName': 'Doe' } }
5
Mirek Rusin

ソリューション1

JSON.stringify(obj)を使用して、比較するオブジェクトの文字列表現を取得します。文字列をファイルに保存します。任意の差分ビューアを使用して、テキストファイルを比較します。

注:JSON.stringifyは、関数定義を指すプロパティを無視します。

ソリューション2

これは、いくつかの変更を加えることで必要なことを実行できます。これは、関数_.isEqual( http://documentcloud.github.com/underscore/ )の変更バージョンです。変更を提案してください! 2つのオブジェクトの最初の違いがどこで発生するかを理解するために作成しました。

// Given two objects find the first key or value not matching, algorithm is a
// inspired by of _.isEqual.
function diffObjects(a, b) {
  console.info("---> diffObjects", {"a": a, "b": b});
  // Check object identity.
  if (a === b) return true;
  // Different types?
  var atype = typeof(a), btype = typeof(b);
  if (atype != btype) {
    console.info("Type mismatch:", {"a": a, "b": b});
    return false;
  };
  // Basic equality test (watch out for coercions).
  if (a == b) return true;
  // One is falsy and the other truthy.
  if ((!a && b) || (a && !b)) {
    console.info("One is falsy and the other truthy:", {"a": a, "b": b});
    return false;
  }
  // Unwrap any wrapped objects.
  if (a._chain) a = a._wrapped;
  if (b._chain) b = b._wrapped;
  // One of them implements an isEqual()?
  if (a.isEqual) return a.isEqual(b);
  // Check dates' integer values.
  if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime();
  // Both are NaN?
  if (_.isNaN(a) && _.isNaN(b)) {
    console.info("Both are NaN?:", {"a": a, "b": b});
    return false;
  }
  // Compare regular expressions.
  if (_.isRegExp(a) && _.isRegExp(b))
    return a.source     === b.source &&
           a.global     === b.global &&
           a.ignoreCase === b.ignoreCase &&
           a.multiline  === b.multiline;
  // If a is not an object by this point, we can't handle it.
  if (atype !== 'object') {
    console.info("a is not an object:", {"a": a});
    return false;
  }
  // Check for different array lengths before comparing contents.
  if (a.length && (a.length !== b.length)) {
    console.info("Arrays are of different length:", {"a": a, "b": b});
    return false;
  }
  // Nothing else worked, deep compare the contents.
  var aKeys = _.keys(a), bKeys = _.keys(b);
  // Different object sizes?
  if (aKeys.length != bKeys.length) {
    console.info("Different object sizes:", {"a": a, "b": b});
    return false;
  }
  // Recursive comparison of contents.
  for (var key in a) if (!(key in b) || !diffObjects(a[key], b[key])) return false;
  return true;
};
4
mozey

NodeJSを使用している場合、このスクリプトにはNPMバージョンもあります。 https://github.com/NV/objectDiff.js

喜ぶ。

3
Lander
var d = { 
   FirstName : "John", 
   LastName : "Doh", 
   Age : 30, 
   EMailAddresses : [
      "[email protected]", 
      "[email protected]"
   ], 
   Children : [ 
      { 
         FirstName : "Sara", 
         LastName : "Doe", 
         Age : 2 
      }, { 
         FirstName : "Beth", 
         LastName : "Doe", 
         Age : 5 
      } 
   ] 
};

var f = { 
   FirstName : "John", 
   LastName : "Doe", 
   Age : 33, 
   EMailAddresses : [
      "[email protected]", 
      "[email protected]"
   ], 
   Children : [ 
      { 
         FirstName : "Sara", 
         LastName : "Doe", 
         Age : 3 
      }, { 
         FirstName : "Bethany", 
         LastName : "Doe", 
         Age : 5 
      } 
   ] 
};

resultobj = []
function comp_obj(t1,t2){
        flag = 1;
        key1_arr = Object.keys(t1)
        key2_arr = Object.keys(t2)
        if(key1_arr.length == key2_arr.length){
                for(key1 in t1){
                        ty1    = Object.prototype.toString.call(t1[key1])
                        ty2    = Object.prototype.toString.call(t2[key1])
                        if(ty1 == ty2) {
                                if(ty1 == '[object String]' || ty1 == '[object Number]' ){
                                        if(t2[key1] != t1[key1]){
                                                flag = 0;
                                                break;
                                        }       
                                }else if(ty1 == '[object Array]'){
                                        var result = comp_arr(t1[key1],t2[key1]);
                                        console.log(ty1,ty2)
                                        if(!result)
                                                flag = 0;
                                }else if(ty1  == '[object Object]'){
                                        var result = comp_obj(t1[key1],t2[key1])
                                        if(!result)
                                                flag = 0;
                                                
                                }
                        }else{
                                flag = 0;
                                break;  
                        }

                }
        }else{
                flag    = 0;
        }
        if(flag)
                return true
        else
                return false;
}
function comp_arr(a,b){
        flag = 1;
        if(a.length == b.length ){
                for(var i=0,l=a.length;i<l;i++){
                        type1    = Object.prototype.toString.call(a[i])
                        type2    = Object.prototype.toString.call(b[i])
                        if(type1 == type2) {
                                if(type1 == '[object String]' || type1 == '[object Number]' ){
                                        if( a[i] != b[i]){
                                                flag = 0;
                                                break;
                                        }       
                                }else if(type1 == '[object Array]'){
                                        var result = comp_arr(a[i],b[i]);
                                        if(!result)
                                                flag = 0;
                                }else if(type1  == '[object Object]'){
                                        var result = comp_obj(a[i],b[i])
                                        if(!result)
                                                flag = 0;
                                }       
                        }else{
                                flag = 0;
                                break;  
                        }
                }
        }else
                 flag = 0;
        if(flag)
                return true
        else
                return false;
}
function create(t,attr,parent_node,innerdata){
        var dom = document.createElement(t)
        for(key in attr){
                dom.setAttribute(key,attr[key])
        }
        dom.innerHTML = innerdata;
        parent_node.appendChild(dom)
        return dom;
}
window.onload = function () {
        for(key in f){
                if(!(key in d)) 
                        resultobj.Push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])})
                type1    = Object.prototype.toString.call(f[key])
                type2    = Object.prototype.toString.call(d[key])
                if(type1 == type2){
                        if(type1 == '[object String]' || type1 == '[object Number]' ){
                                if(f[key] != d[key])
                                        resultobj.Push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])})
                        }else if(type1 == '[object Array]'){
                                var result = comp_arr(f[key],d[key]);
                                if(!result)
                                        resultobj.Push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])})
                        }else if(type1 == '[object Object]'){
                                var result = comp_obj(f[key],d[key])    
                                if(!result)
                                        resultobj.Push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])})
                        }
                }else 
                        resultobj.Push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])})
        }
        var tb = document.getElementById('diff');
        var s1 = document.getElementById('source1');
        var s2 = document.getElementById('source2');
        s1.innerHTML = 'Object 1 :'+ JSON.stringify(f)
        s2.innerHTML = 'Object 2 :'+JSON.stringify(d)
        resultobj.forEach(function(data,i){
                        tr_dom = create('tr',{},tb,'')
                        no = create('td',{},tr_dom,i+1)
                        Object.keys(data).forEach(function(tr){
                                td_dom = create('td',{},tr_dom,data[tr])
                        })
        })
}
  <html>
    <body>
      <p id="source1"> </p>
      <p id="source2"> </p>
      <p id="source7"> DIFFERENCE TABLE</p>
      <table border=''>
        <thead>
          <th>S.no</th>
          <th>Name Of the Key</th>
          <th>Object1 Value</th>
          <th>Object2 Value</th>
        </thead>
        <tbody id="diff">

        </tbody>
      </table>
    </body>
</html>
1
Sudhakar Ayyar

私は最近、これを行うためのモジュールを作成しました。これは、見つけた多数の差分モジュールに満足できなかったためです(最も人気のあるモジュールの束と、モジュールのreadmeでそれらが受け入れられなかった理由をリストしました)。 odiffと呼ばれます: https://github.com/Tixit/odiff 。次に例を示します。

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}]}
]
*/
1
B T

私が見つけたライブラリはどれも十分ではなかったので、私は独自のAngularJSファクトリを作成しました。両方の方法でオブジェクトを比較し、同じ構造内の違いのみを返します。

/**
 * Diff
 * Original author: Danny Coulombe
 * Creation date: 2016-05-18
 * 
 * Work with objects to find their differences.
 */
controllers.factory('diff', [function() {

    var factory = {

        /**
         * Compare the original object with the modified one and return their differences.
         * 
         * @param original: Object
         * @param modified: Object
         * 
         * @return Object
         */
        getDifferences: function(original, modified) {

            var type = modified.constructor === Array ? [] : {};
            var result = angular.copy(type);
            var comparisons = [[original, modified, 1], [modified, original, 0]];

            comparisons.forEach(function(comparison) {

                angular.forEach(comparison[0], function(value, key) {

                    if(result[key] === undefined) {

                        if(comparison[1][key] !== undefined && value !==    null && comparison[1][key] !== null && [Object, Array].indexOf(comparison[1][key].constructor) !== -1) {

                            result[key] = factory.getDifferences(value, comparison[1][key]);
                        }
                        else if(comparison[1][key] !== value) {

                            result[key] = comparison[comparison[2]][key];
                        }

                        if(angular.equals(type, result[key])
                        || result[key] === undefined
                        || (
                            comparison[0][key] !== undefined
                            && result[key] !== null
                            && comparison[0][key] !== null
                            && comparison[0][key].length === comparison[1][key].length
                            && result[key].length === 0
                        )) {
                            delete result[key];
                        }
                    }
                });
            });

            return result;
        }
    };

    return factory;
}]);
0
Danny Coulombe