[javascript] 두 객체 사이의 일반적인 깊은 차이

두 개의 객체가 있습니다 : oldObjnewObj.

의 데이터 oldObj는 양식을 채우는 데 사용되었으며 newObj사용자가이 양식의 데이터를 변경하여 제출 한 결과입니다.

두 물체는 깊습니다. 그들은 객체 또는 객체의 배열 등의 속성을 가지고 있습니다-n 레벨 깊이가 될 수 있으므로 diff 알고리즘은 재귀 적이어야합니다.

지금은 단지에서 (추가와 같이 / 업데이트 / 삭제) 변경이 있었는지 알아낼 필요 oldObjnewObj뿐만 아니라, 최선을 다해 그것을 표현하는 방법.

지금까지 내 생각은 genericDeepDiffBetweenObjects폼에 객체를 반환하는 메서드를 작성하는 {add:{...},upd:{...},del:{...}}것이지만 생각했습니다. 다른 사람이 전에 이것을 필요로 했어야합니다.

그래서 … 누구든지 이것을 할 라이브러리 또는 코드 조각을 알고 있습니까 (아직 JSON 직렬화 가능한 방식으로) 차이를 나타내는 더 나은 방법이 있습니까?

최신 정보:

와 동일한 객체 구조를 사용 newObj하지만 모든 속성 값을 양식의 객체로 변환 하여 업데이트 된 데이터를 나타내는 더 좋은 방법을 생각했습니다 .

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

경우에 따라서 newObj.prop1 = 'new value'그리고 oldObj.prop1 = 'old value'그것을 설정합니다returnObj.prop1 = {type: 'update', data: 'new value'}

업데이트 2 :

그것은 배열이 있기 때문에 우리가 배열 인 속성에 도착하면 진정으로 털이 얻을 [1,2,3]동일로 간주되어야한다 [2,3,1]문자열, INT 및 부울과 같은 값 기반 형의 배열에 대한 간단한 충분히 인,하지만 올 때 처리하기 정말 어려운 도착 객체 및 배열과 같은 참조 유형의 배열.

동일해야하는 배열 예 :

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

이러한 유형의 깊은 가치 평등을 확인하는 것뿐만 아니라 변경 사항을 나타내는 좋은 방법을 찾는 것도 매우 복잡합니다.



답변

나는 당신이 원하는 것을하고있는 작은 수업을 썼으며 여기서 테스트 할 수 있습니다 .

귀하의 제안과 다른 점은 [1,[{c: 1},2,3],{a:'hey'}] and [{a:'hey'},1,[3,{c: 1},2]]동일한 것으로 간주하지 않는다는 것입니다. 요소의 순서가 같지 않으면 배열이 같지 않다고 생각하기 때문입니다. 물론 필요한 경우 변경할 수 있습니다. 또한이 코드는 전달 된 기본 값 (이 작업은 “compareValues”메소드에 의해 수행됨)에 따라 임의의 방식으로 diff 오브젝트를 형식화하는 데 사용되는 인수로 기능을 수행하도록 추가로 향상 될 수 있습니다.

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

전체 example + tests + mixins에 대해서는 http://jsfiddle.net/drzaus/9g5qoxwj/ 를 참조하십시오


답변

ES6 솔루션을 제공하고 싶습니다 … 단방향 diff o2입니다 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]
  }
}, {})


답변

Lodash 사용하기 :

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

키 / 객체 / 소스를 사용하지 않지만 액세스 해야하는 경우 키를 남겨 두었습니다. 객체 비교는 콘솔이 가장 바깥 쪽 요소에서 가장 안쪽 요소까지 콘솔의 차이점을 인쇄하지 못하게합니다.

배열을 처리하기 위해 내부에 논리를 추가 할 수 있습니다. 아마도 배열을 먼저 정렬하십시오. 이것은 매우 유연한 솔루션입니다.

편집하다

lodash 업데이트로 인해 _.merge에서 _.mergeWith로 변경되었습니다. 변경 사항을 확인한 Aviron에게 감사합니다.


답변

다음은 두 JavaScript 객체 사이의 차이점을 찾는 데 사용할 수있는 JavaScript 라이브러리입니다.

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

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

브라우저와 Node.js에서 재귀 -diff 라이브러리를 사용할 수 있습니다. 브라우저의 경우 다음을 수행하십시오.

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

node.js에서 ‘recursive-diff’모듈이 필요하고 아래처럼 사용할 수 있습니다.

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


답변

요즘에는 사용할 수있는 모듈이 꽤 있습니다. 내가 찾은 수많은 diffing 모듈에 만족하지 않았기 때문에 최근 에이 작업을 수행하기 위해 모듈을 작성했습니다. 해당 호출 odiff: https://github.com/Tixit/odiff . 또한 가장 인기있는 모듈과 readme에서 허용되지 않는 이유를 나열했습니다 . 원하는 속성이없는 odiff경우 살펴볼 수 있습니다 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}]}
]
*/


답변

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

매주 5 만 회 이상 다운로드되는 npm 모듈이 있습니다 : https://www.npmjs.com/package/deep-object-diff

차이점의 표현과 같은 객체를 좋아합니다. 특히 형식이 지정되면 구조를 쉽게 볼 수 있습니다.

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