我有两个对象: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]]

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


当前回答

从sbgoran的答案中得到扩展和简化的函数。 这允许深度扫描和发现数组的相似性。

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

其他回答

使用Lodash:

_。合并(oldObj, newObj, function (objectValue, sourceValue, key, object, source) { 如果(!)(_。isEqual(objectValue, sourceValue)) && (Object(objectValue) ! console.log(key + "\n Expected: " + sourceValue + "\n Actual: " + objectValue); } });

我不使用key/object/source,但我把它留在那里,如果你需要访问它们。 对象比较只是防止控制台将最外层元素与最内部元素之间的差异打印到控制台。

您可以在内部添加一些逻辑来处理数组。也许先对数组排序。这是一个非常灵活的解决方案。

EDIT

由_更改。合并到_。mergeWith由于lodash更新。感谢Aviron注意到这个变化。

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

我写了一个小类来做你想做的,你可以在这里测试。

唯一不同于你的建议的是我不考虑

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

and

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

相等,因为我认为数组中元素的顺序不相等。当然,如果需要,这可以改变。此外,这段代码还可以进一步增强,将函数作为参数,用于基于传递的原语值以任意方式格式化diff对象(现在这项工作由"compareValues"方法完成)。

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

var base = [
{"value": "01", "label": "Pendências"},
{"value": "02", "label": "Ambulatório"},
{"value": "03", "label": "Urgência"},
{"value": "04", "label": "Clínica Médica"},
{"value": "05", "label": "Revisão"},
{"value": "06", "label": "Imagens"},
];

var used = [
{"value": "01", "label": "Pendências"},
{"value": "02", "label": "Ambulatório"},
{"value": "03", "label": "Urgência"},
{"value": "04", "label": "Clínica Médica"},
];

function diff(obj1,obj2) {
        var temp = JSON.stringify(obj2.map((x)=> x.value));
    return obj1.filter((y)=> temp.indexOf(y.value)<0 && y);
}


var result = diff(base, used); 
console.clear();
console.log('RESULTADO');
console.log(result);

codeped

下面是@sbgoran代码的typescript版本

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;

  }
}