lodash를 사용하여 두 개체를 심도있게 비교하는 방법은 무엇입니까?


309

다른 중첩 된 객체가 2 개 있으며 중첩 된 속성 중 하나에 차이가 있는지 알아야합니다.

var a = {};
var b = {};

a.prop1 = 2;
a.prop2 = { prop3: 2 };

b.prop1 = 2;
b.prop2 = { prop3: 3 };

더 중첩 된 속성으로 인해 개체가 훨씬 복잡해질 수 있습니다. 그러나 이것은 좋은 예입니다. 재귀 함수 또는 lodash와 함께 사용할 수있는 옵션이 있습니다 ...



7
_.isEqual(value, other)두 값을 비교하여 두 값이 같은지 확인합니다. lodash.com/docs#isEqual
Lukas Liesis

JSON.stringify ()
xgqfrms 2

10
JSON.stringify ()가 잘못되었습니다 : JSON.stringify ({a : 1, b : 2})! == JSON.stringify ({b : 2, a : 1})
Shl

답변:


475

쉽고 우아한 솔루션을 사용 _.isEqual하면 깊게 비교할 수 있습니다.

var a = {};
var b = {};

a.prop1 = 2;
a.prop2 = { prop3: 2 };

b.prop1 = 2;
b.prop2 = { prop3: 3 };

_.isEqual(a, b); // returns false if different

그러나이 솔루션은 어떤 속성이 다른지 보여주지 않습니다.

http://jsfiddle.net/bdkeyn0h/


2
나는 대답이 꽤 오래되었다는 것을 알고 있지만 추가하고 싶기 때문에 _.isEqual까다로울 수 있습니다. 객체를 복사하고 일부 값을 변경하면 참조가 동일하기 때문에 여전히 참으로 표시됩니다. 따라서이 기능을 사용할 때는주의해야합니다.
oruckdeschel

23
@oruckdeschel 참조가 동일하면 동일한 객체입니다. 따라서 동일합니다. 이것은 까다 롭지 않은 까다로운 포인터입니다. lodash는 굉장합니다.
guy mograbi

265

어떤 속성이 다른지 알아야하는 경우 reduce ()를 사용하십시오 .

_.reduce(a, function(result, value, key) {
    return _.isEqual(value, b[key]) ?
        result : result.concat(key);
}, []);
// → [ "prop2" ]

36
이렇게하면 첫 번째 수준의 다른 속성 만 출력됩니다. (따라서 다른 속성을 출력한다는 의미에서 실제로 깊지 는 않습니다 .)
Bloke

16
또한 이것은 a에없는 b의 속성을 선택하지 않습니다.
Ed Staub

3
그리고 _.reduce(a, (result, value, key) => _.isEqual(value, b[key]) ? result : result.concat(key), [])한 라인 ES6 솔루션
Dotgreg

1
key : value를 연결하는 버전let edited = _.reduce(a, function(result, value, key) { return _.isEqual(value, b[key]) ? result : result.concat( { [key]: value } ); }, []);
Aline Matos

47

이 스레드를 다루는 모든 사람을 위해 더 완벽한 솔루션이 있습니다. 그것은 비교합니다 두 개체를 당신에게 중 모든 속성의 키주는 유일한 오브젝트 1 년을 , 단지 object2에서 , 또는이다 오브젝트 1 및 object2 모두 있지만 다른 값을 가질 :

/*
 * Compare two objects by reducing an array of keys in obj1, having the
 * keys in obj2 as the intial value of the result. Key points:
 *
 * - All keys of obj2 are initially in the result.
 *
 * - If the loop finds a key (from obj1, remember) not in obj2, it adds
 *   it to the result.
 *
 * - If the loop finds a key that are both in obj1 and obj2, it compares
 *   the value. If it's the same value, the key is removed from the result.
 */
function getObjectDiff(obj1, obj2) {
    const diff = Object.keys(obj1).reduce((result, key) => {
        if (!obj2.hasOwnProperty(key)) {
            result.push(key);
        } else if (_.isEqual(obj1[key], obj2[key])) {
            const resultKeyIndex = result.indexOf(key);
            result.splice(resultKeyIndex, 1);
        }
        return result;
    }, Object.keys(obj2));

    return diff;
}

다음은 예제 출력입니다.

// Test
let obj1 = {
    a: 1,
    b: 2,
    c: { foo: 1, bar: 2},
    d: { baz: 1, bat: 2 }
}

let obj2 = {
    b: 2, 
    c: { foo: 1, bar: 'monkey'}, 
    d: { baz: 1, bat: 2 }
    e: 1
}
getObjectDiff(obj1, obj2)
// ["c", "e", "a"]

중첩 된 객체를 신경 쓰지 않고 lodash를 건너 뛰려면 _.isEqual일반적인 값 비교를 대신 할 수 있습니다 (예 :) obj1[key] === obj2[key].


이 선택된 답변은 평등을 테스트하기 만하면됩니다. 차이점이 무엇인지 알아야 할 경우에는 분명한 방법이 없지만이 대답은 차이가있는 최상위 속성 키 목록 만 제공하면 좋습니다. (그리고 그것은 함수로서 답을 제공하여 그것을 사용할 수있게한다.)
Sigfried

이 작업과 _.isEqual (obj1, obj2) 사용의 차이점은 무엇입니까? hasOwnProperty에 대한 검사를 추가하면 _.isEqual이 수행하지 않는 것은 무엇입니까? obj1에 obj2에없는 속성이 있으면 _.isEqual이 true를 반환하지 않는다는 가정하에있었습니다.
Jaked222

2
@ Jaked222-차이점은 isEqual은 객체가 같은지 여부를 알려주는 부울을 반환한다는 입니다. 위의 함수 는 두 객체 (다른 경우) 사이의 차이점을 알려줍니다 . 두 객체가 동일한 지 여부 만 알고 싶다면 isEqual이면 충분합니다. 그러나 많은 경우에 두 객체 사이의 차이점이 무엇인지 알고 싶습니다. 예를 들어 무언가 전후에 변경 사항을 감지 한 다음 변경 사항을 기반으로 이벤트를 전달하려는 경우를 예로들 수 있습니다.
Johan Persson

30

Adam Boduch의 답변을 바탕으로 기능을 가장 깊은 의미에서 비교 하는이 기능을 작성했습니다. 다른 값을 가진 경로와 하나 또는 다른 객체에서 누락 된 경로를 반환 .

이 코드는 효율성을 염두에두고 작성되지 않았으며 그 점에서 개선 된 부분이 가장 환영 받지만 기본 형식은 다음과 같습니다.

var compare = function (a, b) {

  var result = {
    different: [],
    missing_from_first: [],
    missing_from_second: []
  };

  _.reduce(a, function (result, value, key) {
    if (b.hasOwnProperty(key)) {
      if (_.isEqual(value, b[key])) {
        return result;
      } else {
        if (typeof (a[key]) != typeof ({}) || typeof (b[key]) != typeof ({})) {
          //dead end.
          result.different.push(key);
          return result;
        } else {
          var deeper = compare(a[key], b[key]);
          result.different = result.different.concat(_.map(deeper.different, (sub_path) => {
            return key + "." + sub_path;
          }));

          result.missing_from_second = result.missing_from_second.concat(_.map(deeper.missing_from_second, (sub_path) => {
            return key + "." + sub_path;
          }));

          result.missing_from_first = result.missing_from_first.concat(_.map(deeper.missing_from_first, (sub_path) => {
            return key + "." + sub_path;
          }));
          return result;
        }
      }
    } else {
      result.missing_from_second.push(key);
      return result;
    }
  }, result);

  _.reduce(b, function (result, value, key) {
    if (a.hasOwnProperty(key)) {
      return result;
    } else {
      result.missing_from_first.push(key);
      return result;
    }
  }, result);

  return result;
}

이 스 니펫을 사용하여 코드를 시도 할 수 있습니다 (전체 페이지 모드에서 실행하는 것이 좋습니다).


4
난 그냥 버그를 수정,하지만 당신은 객체 내에서 키의 존재를 확인해야합니다, 당신이 알 수 있도록 b 사용 b.hasOwnProperty(key)또는key in b 하지와 함께 b[key] != undefined. 사용 된 이전 버전 b[key] != undefined에서 함수는에서와 같이을 포함하는 객체에 대해 잘못된 diff를 반환 undefined했습니다 compare({disabled: undefined}, {disabled: undefined}). 사실, 이전 버전에도 문제가있었습니다 null. 당신은 항상 사용하여 같은 문제를 방지 할 수 ===!== 대신 ==하고 !=.
Rory O'Kane

23

간결한 솔루션은 다음과 같습니다.

_.differenceWith(a, b, _.isEqual);

7
나를 위해 물건으로 작동하지 않는 것 같습니다. 대신 빈 배열을 반환합니다.
tomhughes

2
또한 Lodash 4.17.4와 빈 배열을 얻기
aristidesfl

@ Z.Khullah이 방법으로 작동 한 경우 문서화되어 있지 않습니다.
Brendon

1
@Brendon, @THughes, @aristidesfl 죄송합니다. 여러 가지를 혼합하여 객체의 배열과 작동하지만 깊은 객체 비교에는 적합하지 않습니다. 결과적으로 매개 변수가 모두 배열이 아닌 경우 lodash는을 반환 []합니다.
Z. Khullah

7

객체가 다른 객체와 어떻게 다른지 재귀 적으로 보여주기 위해 _.isduce_.isEqual_.isPlainObject를 사용할 수 있습니다 . 이 경우 a와 b의 차이점 또는 b와 a의 차이점을 비교할 수 있습니다.

var a = {prop1: {prop1_1: 'text 1', prop1_2: 'text 2', prop1_3: [1, 2, 3]}, prop2: 2, prop3: 3};
var b = {prop1: {prop1_1: 'text 1', prop1_3: [1, 2]}, prop2: 2, prop3: 4};

var diff = function(obj1, obj2) {
  return _.reduce(obj1, function(result, value, key) {
    if (_.isPlainObject(value)) {
      result[key] = diff(value, obj2[key]);
    } else if (!_.isEqual(value, obj2[key])) {
      result[key] = value;
    }
    return result;
  }, {});
};

var res1 = diff(a, b);
var res2 = diff(b, a);
console.log(res1);
console.log(res2);
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js"></script>


7

간단한 사용 _.isEqual방법, 그것은 모든 비교를 위해 작동합니다 ...

  • 참고 : 이 방법은 배열, 배열 버퍼, 부울, * 날짜 오브젝트, 오류 오브젝트, 맵, 숫자, Object오브젝트, 정규 표현식, * 세트, 문자열, 기호 및 유형 배열 비교를 지원합니다. Object객체는 상속 가능하지 않고 열거 가능한 속성으로 * 비교됩니다. 함수 및 DOM * 노드는 지원 되지 않습니다 .

아래에 있다면 :

 const firstName = {name: "Alireza"};
 const otherName = {name: "Alireza"};

당신이 할 경우 : _.isEqual(firstName, otherName); ,

돌아올 것이다 사실 합니다

그리고 만약 const fullName = {firstName: "Alireza", familyName: "Dezfoolian"};

당신이 할 경우 : _.isEqual(firstName, fullName); ,

거짓 을 반환합니다


6

이 코드는 값이 다르고 두 객체의 값이 모두 다른 모든 속성을 가진 객체를 반환합니다. 차이를 기록하는 데 유용합니다.

var allkeys = _.union(_.keys(obj1), _.keys(obj2));
var difference = _.reduce(allkeys, function (result, key) {
  if ( !_.isEqual(obj1[key], obj2[key]) ) {
    result[key] = {obj1: obj1[key], obj2: obj2[key]}
  }
  return result;
}, {});

3

lodash / 밑줄을 사용하지 않고이 코드를 작성했으며 object1과 object2를 자세히 비교하기 위해 잘 작동합니다.

function getObjectDiff(a, b) {
    var diffObj = {};
    if (Array.isArray(a)) {
        a.forEach(function(elem, index) {
            if (!Array.isArray(diffObj)) {
                diffObj = [];
            }
            diffObj[index] = getObjectDiff(elem, (b || [])[index]);
        });
    } else if (a != null && typeof a == 'object') {
        Object.keys(a).forEach(function(key) {
            if (Array.isArray(a[key])) {
                var arr = getObjectDiff(a[key], b[key]);
                if (!Array.isArray(arr)) {
                    arr = [];
                }
                arr.forEach(function(elem, index) {
                    if (!Array.isArray(diffObj[key])) {
                        diffObj[key] = [];
                    }
                    diffObj[key][index] = elem;
                });
            } else if (typeof a[key] == 'object') {
                diffObj[key] = getObjectDiff(a[key], b[key]);
            } else if (a[key] != (b || {})[key]) {
                diffObj[key] = a[key];
            } else if (a[key] == (b || {})[key]) {
                delete a[key];
            }
        });
    }
    Object.keys(diffObj).forEach(function(key) {
        if (typeof diffObj[key] == 'object' && JSON.stringify(diffObj[key]) == '{}') {
            delete diffObj[key];
        }
    });
    return diffObj;
}

3

확인을 위해 (중첩) 속성 템플릿을 사용하여 심층 비교

function objetcsDeepEqualByTemplate(objectA, objectB, comparisonTemplate) {
  if (!objectA || !objectB) return false

  let areDifferent = false
  Object.keys(comparisonTemplate).some((key) => {
    if (typeof comparisonTemplate[key] === 'object') {
      areDifferent = !objetcsDeepEqualByTemplate(objectA[key], objectB[key], comparisonTemplate[key])
      return areDifferent
    } else if (comparisonTemplate[key] === true) {
      areDifferent = objectA[key] !== objectB[key]
      return areDifferent
    } else {
      return false
    }
  })

  return !areDifferent
}

const objA = { 
  a: 1,
  b: {
    a: 21,
    b: 22,
  },
  c: 3,
}

const objB = { 
  a: 1,
  b: {
    a: 21,
    b: 25,
  },
  c: true,
}

// template tells which props to compare
const comparisonTemplateA = {
  a: true,
  b: {
    a: true
  }
}
objetcsDeepEqualByTemplate(objA, objB, comparisonTemplateA)
// returns true

const comparisonTemplateB = {
  a: true,
  c: true
}
// returns false
objetcsDeepEqualByTemplate(objA, objB, comparisonTemplateB)

이것은 콘솔에서 작동합니다. 필요한 경우 어레이 지원을 추가 할 수 있습니다


2

나는 깊은 보풀을 출력하기 위해 Adam Boduch의 코드를 찔렀습니다.

function diff (obj1, obj2, path) {
    obj1 = obj1 || {};
    obj2 = obj2 || {};

    return _.reduce(obj1, function(result, value, key) {
        var p = path ? path + '.' + key : key;
        if (_.isObject(value)) {
            var d = diff(value, obj2[key], p);
            return d.length ? result.concat(d) : result;
        }
        return _.isEqual(value, obj2[key]) ? result : result.concat(p);
    }, []);
}

diff({ foo: 'lol', bar: { baz: true }}, {}) // returns ["foo", "bar.baz"]

1
obj1과 obj2의 순서가 중요하다는 것이 매력처럼 작동합니다. 예 : diff({}, { foo: 'lol', bar: { baz: true }}) // returns []
amangpt777

2

요청에 따라 재귀 객체 비교 기능이 있습니다. 그리고 조금 더. 이러한 기능을 주로 사용하는 것이 객체 검사라고 가정하면 할 말이 있습니다. 약간의 차이점이 무의미한 경우 완전한 심층 비교는 나쁜 생각입니다. 예를 들어, TDD 어설 션에서 맹목적으로 비교하면 테스트가 불필요 해지기 쉽습니다. 이런 이유로 훨씬 더 유용한 부분 diff 를 소개하고 싶습니다 . 이 스레드에 대한 이전의 기여와 재귀적인 유사체입니다. 에없는 키는 무시합니다 .

var bdiff = (a, b) =>
    _.reduce(a, (res, val, key) =>
        res.concat((_.isPlainObject(val) || _.isArray(val)) && b
            ? bdiff(val, b[key]).map(x => key + '.' + x) 
            : (!b || val != b[key] ? [key] : [])),
        []);

BDiff를 사용하면 자동 검사에 원하는 다른 속성을 허용하면서도 예상 값을 확인할 수 있습니다 . 이를 통해 모든 종류의 고급 어설 션을 작성할 수 있습니다. 예를 들면 다음과 같습니다.

var diff = bdiff(expected, actual);
// all expected properties match
console.assert(diff.length == 0, "Objects differ", diff, expected, actual);
// controlled inequality
console.assert(diff.length < 3, "Too many differences", diff, expected, actual);

완전한 솔루션으로 돌아갑니다. bdiff를 사용하여 전통적인 diff를 만드는 것은 쉽지 않습니다.

function diff(a, b) {
    var u = bdiff(a, b), v = bdiff(b, a);
    return u.filter(x=>!v.includes(x)).map(x=>' < ' + x)
    .concat(u.filter(x=>v.includes(x)).map(x=>' | ' + x))
    .concat(v.filter(x=>!u.includes(x)).map(x=>' > ' + x));
};

두 개의 복잡한 객체에서 위의 기능을 실행하면 다음과 비슷한 결과가 출력됩니다.

 [
  " < components.0.components.1.components.1.isNew",
  " < components.0.cryptoKey",
  " | components.0.components.2.components.2.components.2.FFT.min",
  " | components.0.components.2.components.2.components.2.FFT.max",
  " > components.0.components.1.components.1.merkleTree",
  " > components.0.components.2.components.2.components.2.merkleTree",
  " > components.0.components.3.FFTResult"
 ]

마지막으로, 값이 어떻게 다른지 엿볼 수 있도록 diff 출력 을 직접 eval () 할 수 있습니다 . 이를 위해서는 구문 적으로 올바른 경로를 출력 하는 못생긴 bdiff 버전이 필요 합니다.

// provides syntactically correct output
var bdiff = (a, b) =>
    _.reduce(a, (res, val, key) =>
        res.concat((_.isPlainObject(val) || _.isArray(val)) && b
            ? bdiff(val, b[key]).map(x => 
                key + (key.trim ? '':']') + (x.search(/^\d/)? '.':'[') + x)
            : (!b || val != b[key] ? [key + (key.trim ? '':']')] : [])),
        []);

// now we can eval output of the diff fuction that we left unchanged
diff(a, b).filter(x=>x[1] == '|').map(x=>[x].concat([a, b].map(y=>((z) =>eval('z.' + x.substr(3))).call(this, y)))));

다음과 비슷한 결과가 출력됩니다.

[" | components[0].components[2].components[2].components[2].FFT.min", 0, 3]
[" | components[0].components[2].components[2].components[2].FFT.max", 100, 50]

MIT 라이센스;)


1

Adam Boduch의 답변을 완성하면 속성이 다릅니다.

const differenceOfKeys = (...objects) =>
  _.difference(...objects.map(obj => Object.keys(obj)));
const differenceObj = (a, b) => 
  _.reduce(a, (result, value, key) => (
    _.isEqual(value, b[key]) ? result : [...result, key]
  ), differenceOfKeys(b, a));

1

키 비교 만 필요한 경우 :

 _.reduce(a, function(result, value, key) {
     return b[key] === undefined ? key : []
  }, []);

0

다음은 Lodash 딥 차이 검사기를 사용하는 간단한 Typescript입니다.이 스크립트는 이전 개체와 새 개체의 차이점만으로 새 개체를 생성합니다.

예를 들어 다음과 같은 경우

const oldData = {a: 1, b: 2};
const newData = {a: 1, b: 3};

결과 객체는 다음과 같습니다.

const result: {b: 3};

또한 다중 수준의 딥 객체와 호환되므로 배열의 경우 약간의 조정이 필요할 수 있습니다.

import * as _ from "lodash";

export const objectDeepDiff = (data: object | any, oldData: object | any) => {
  const record: any = {};
  Object.keys(data).forEach((key: string) => {
    // Checks that isn't an object and isn't equal
    if (!(typeof data[key] === "object" && _.isEqual(data[key], oldData[key]))) {
      record[key] = data[key];
    }
    // If is an object, and the object isn't equal
    if ((typeof data[key] === "object" && !_.isEqual(data[key], oldData[key]))) {
      record[key] = objectDeepDiff(data[key], oldData[key]);
    }
  });
  return record;
};

-1
var isEqual = function(f,s) {
  if (f === s) return true;

  if (Array.isArray(f)&&Array.isArray(s)) {
    return isEqual(f.sort(), s.sort());
  }
  if (_.isObject(f)) {
    return isEqual(f, s);
  }
  return _.isEqual(f, s);
};

유효하지 않습니다. 프로토 타입을 비교하기 때문에 객체를 ===직접 비교할 수 없으며 { a: 20 } === { a: 20 }false를 반환합니다. 객체를 주로 비교하는 더 올바른 방법은 객체를 감싸는 것입니다.JSON.stringify()
Herrgott

(f === s) true를 반환하면; -재귀 전용입니다. 예 a : 20} === {a : 20}은 false를 반환하고 다음 조건으로 이동합니다
Crusader

왜 안돼 _.isEqual(f, s)? :)
Herrgott

f객체가 있고 if (_.isObject(f))함수에 도달하면 해당 포인트를 다시 누르기 때문에 무한 재귀 루프가 발생 합니다. 동일f (Array.isArray(f)&&Array.isArray(s))
rady

-2

이것은 lodash 를 사용하여 @JLavoie를 기반으로 했습니다 .

let differences = function (newObj, oldObj) {
      return _.reduce(newObj, function (result, value, key) {
        if (!_.isEqual(value, oldObj[key])) {
          if (_.isArray(value)) {
            result[key] = []
            _.forEach(value, function (innerObjFrom1, index) {
              if (_.isNil(oldObj[key][index])) {
                result[key].push(innerObjFrom1)
              } else {
                let changes = differences(innerObjFrom1, oldObj[key][index])
                if (!_.isEmpty(changes)) {
                  result[key].push(changes)
                }
              }
            })
          } else if (_.isObject(value)) {
            result[key] = differences(value, oldObj[key])
          } else {
            result[key] = value
          }
        }
        return result
      }, {})
    }

https://jsfiddle.net/EmilianoBarboza/0g0sn3b9/8/


-2

그냥 바닐라 js를 사용하여

let a = {};
let b = {};

a.prop1 = 2;
a.prop2 = { prop3: 2 };

b.prop1 = 2;
b.prop2 = { prop3: 3 };

JSON.stringify(a) === JSON.stringify(b);
// false
b.prop2 = { prop3: 2};

JSON.stringify(a) === JSON.stringify(b);
// true

여기에 이미지 설명을 입력하십시오


1
이 방법은 어떤 속성이 다른지 알려주지 않습니다.
JLavoie '10

2
이 경우 속성 순서가 결과에 영향을줍니다.
빅터 올리베이라

-2

에 따라 빌드에 스리 Gudimela의 대답 , 여기가 흐름 행복을 만들거야 방식으로 업데이트됩니다

"use strict"; /* @flow */



//  E X P O R T

export const objectCompare = (objectA: any, objectB: any) => {
  let diffObj = {};

  switch(true) {
    case (Array.isArray(objectA)):
      objectA.forEach((elem, index) => {
        if (!Array.isArray(diffObj))
          diffObj = [];

        diffObj[index] = objectCompare(elem, (objectB || [])[index]);
      });

      break;

    case (objectA !== null && typeof objectA === "object"):
      Object.keys(objectA).forEach((key: any) => {
        if (Array.isArray(objectA[key])) {
          let arr = objectCompare(objectA[key], objectB[key]);

          if (!Array.isArray(arr))
            arr = [];

          arr.forEach((elem, index) => {
            if (!Array.isArray(diffObj[key]))
              diffObj[key] = [];

            diffObj[key][index] = elem;
          });
        } else if (typeof objectA[key] === "object")
          diffObj[key] = objectCompare(objectA[key], objectB[key]);
        else if (objectA[key] !== (objectB || {})[key])
          diffObj[key] = objectA[key];
        else if (objectA[key] === (objectB || {})[key])
          delete objectA[key];
      });

      break;

    default:
      break;
  }

  Object.keys(diffObj).forEach((key: any) => {
    if (typeof diffObj[key] === "object" && JSON.stringify(diffObj[key]) === "{}")
      delete diffObj[key];
  });

  return diffObj;
};
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.