我有两个对象:oldObj和newObj。

oldObj中的数据用于填充表单,而newObj是用户更改该表单中的数据并提交的结果。

这两个物体都很深。它们具有对象或对象数组等属性-它们可以有n层深,因此diff算法需要递归。

现在我不仅需要弄清楚从oldObj到newObj更改了什么(如添加/更新/删除),而且还需要知道如何最好地表示它。

到目前为止,我的想法只是构建一个通用的deepdiffbetweenobjects方法,该方法将返回窗体上的对象{add:{…},upd:{…},del:{…但我转念一想:以前一定有人需要它。

所以…有没有人知道一个库或一段代码可以做到这一点,并且可能有更好的方式来表示差异(以一种仍然是JSON可序列化的方式)?

更新:

我想到了一个更好的方法来表示更新的数据,通过使用与newObj相同的对象结构,但将所有属性值转换为窗体上的对象:

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

如果newObj。prop1 = 'new value'和oldObj。prop1 = 'old value'它将设置returnbj。Prop1 = {type: 'update', data: 'new value'}

更新2:

当我们处理数组的属性时,它会变得非常麻烦,因为数组[1,2,3]应该被计算为等于[2,3,1],这对于基于值的类型的数组(如string, int和bool)来说足够简单,但是当涉及到引用类型的数组(如对象和数组)时就很难处理了。

数组的示例应该是相等的:

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

不仅要检查这种类型的深度值相等相当复杂,而且要找出一种表示可能发生的变化的好方法。


当前回答

我已经使用这段代码来完成你所描述的任务:

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

这将为您提供一个新对象,该对象将合并窗体中的旧对象和新对象之间的所有更改

其他回答

我已经为我的一个项目写了一个函数,它将对象作为用户选项与其内部克隆进行比较。 它还可以验证,甚至替换默认值,如果用户输入坏类型的数据或删除,在纯javascript。

在IE8中100%有效。测试成功。

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

/ *结果

{
  "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"
}

*/

使用下划线,一个简单的差异:

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

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

o1中对应但o2值不同的部分的结果:

{a: 1, b: 2}

如果是深差的话就不一样了

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在评论中指出的那样,上面只是一个diff a- >b,并且不可逆(这意味着b中的额外属性将被忽略)。用a——>b——>a代替:

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

完整的示例+tests+mixins请参见http://jsfiddle.net/drzaus/9g5qoxwj/

function Difference (ob1,ob2){   
 let ob3={}  
 let status=false  
  for ( var a1 in ob1 ) {  
    for (var a2 in ob2){  
      if (a1===a2 && ob1[a1]===ob2[a2]){     
        status=true;   
        break;   
      };
    };    
    if (status===false){    
      if (ob1[a2]===undefined){    
        ob3[a1]="ob1:"+ob1[a1]+", ob2:"+ob2[a1];    
      };    
      if ( ob2[a1]===undefined){    
        ob3[a2]="ob1:"+ob1[a2]+", ob2:"+ob2[a2];    
      }else {    
        ob3[a1]="ob1:"+ob1[a1] +", ob2:"+ob2[a1];    
      };     
    }else {   
      status=false;
    };   
  };     
  console.log(ob3);    
};

我为我自己的用例(es5环境)编写了这个,认为这可能对某些人有用,所以它是:

function deepCompare(obj1, obj2) {
    var diffObj = Array.isArray(obj2) ? [] : {}
    Object.getOwnPropertyNames(obj2).forEach(function(prop) {
        if (typeof obj2[prop] === 'object') {
            diffObj[prop] = deepCompare(obj1[prop], obj2[prop])
            // if it's an array with only length property => empty array => delete
            // or if it's an object with no own properties => delete
            if (Array.isArray(diffObj[prop]) && Object.getOwnPropertyNames(diffObj[prop]).length === 1 || Object.getOwnPropertyNames(diffObj[prop]).length === 0) {
                delete diffObj[prop]
            }
        } else if(obj1[prop] !== obj2[prop]) {
            diffObj[prop] = obj2[prop]
        }
    });
    return diffObj
}

这可能不是很有效,但是会根据第二个Obj输出一个只有不同道具的对象。

我只是使用ramda,为了解决同样的问题,我需要知道在新对象中有什么改变。这是我的设计。

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)

结果是属性的名称和状态。

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