我有2个不同的嵌套对象,我需要知道它们是否在其中一个嵌套属性中有不同。

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

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

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

对象可以更复杂,有更多嵌套的属性。但这是一个很好的例子。我可以选择使用递归函数或lodash的东西…


当前回答

简单使用_。isEqual方法,它将适用于所有比较…

注意:此方法支持比较数组、数组缓冲区、 布尔值、 *日期对象,错误对象,地图,数字,对象对象,正则表达式, *集合、字符串、符号和类型化数组。对象对象进行比较 *通过自身的,不可继承的,可枚举的属性。函数和DOM *节点不支持。

所以如果你有以下情况:

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

如果是:_。isEqual (firstName, otherName);

它会返回true

如果const fullName = {firstName: "Alireza", familyName: "Dezfoolian"};

如果是:_。isEqual (firstName, fullName);

将返回false

其他回答

As it was asked, here's a recursive object comparison function. And a bit more. Assuming that primary use of such function is object inspection, I have something to say. Complete deep comparison is a bad idea when some differences are irrelevant. For example, blind deep comparison in TDD assertions makes tests unnecessary brittle. For that reason, I'd like to introduce a much more valuable partial diff. It is a recursive analogue of a previous contribution to this thread. It ignores keys not present in a

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

最后,为了了解值之间的差异,我们可能需要直接eval() diff输出。为此,我们需要一个更丑的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]

“一族”许可

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

您可以使用以下代码段(建议以全页模式运行)尝试代码:

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; } var a_editor = new JSONEditor($('#a')[0], { name: 'a', mode: 'code' }); var b_editor = new JSONEditor($('#b')[0], { name: 'b', mode: 'code' }); var a = { same: 1, different: 2, missing_from_b: 3, missing_nested_from_b: { x: 1, y: 2 }, nested: { same: 1, different: 2, missing_from_b: 3 } } var b = { same: 1, different: 99, missing_from_a: 3, missing_nested_from_a: { x: 1, y: 2 }, nested: { same: 1, different: 99, missing_from_a: 3 } } a_editor.set(a); b_editor.set(b); var result_editor = new JSONEditor($('#result')[0], { name: 'result', mode: 'view' }); var do_compare = function() { var a = a_editor.get(); var b = b_editor.get(); result_editor.set(compare(a, b)); } #objects {} #objects section { margin-bottom: 10px; } #objects section h1 { background: #444; color: white; font-family: monospace; display: inline-block; margin: 0; padding: 5px; } .jsoneditor-outer, .ace_editor { min-height: 230px !important; } button:hover { background: orangered; } button { cursor: pointer; background: red; color: white; text-align: left; font-weight: bold; border: 5px solid crimson; outline: 0; padding: 10px; margin: 10px 0px; } <link href="https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/5.5.10/jsoneditor.min.css" rel="stylesheet" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/5.5.10/jsoneditor.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <div id="objects"> <section> <h1>a (first object)</h1> <div id="a"></div> </section> <section> <h1>b (second object)</h1> <div id="b"></div> </section> <button onClick="do_compare()">compare</button> <section> <h1>result</h1> <div id="result"></div> </section> </div>

我们需要在两个json更新之间获取delta,以跟踪数据库更新。也许其他人会觉得这很有用。

https://gist.github.com/jp6rt/7fcb6907e159d7851c8d59840b669e3d

const {
  isObject,
  isEqual,
  transform,
  has,
  merge,
} = require('lodash');
const assert = require('assert');

/**
 * Perform a symmetric comparison on JSON object.
 * @param {*} baseObj - The base object to be used for comparison against the withObj.
 * @param {*} withObj - The withObject parameter is used as the comparison on the base object.
 * @param {*} invert  - Because this is a symmetric comparison. Some values in the with object
 *                      that doesn't exist on the base will be lost in translation.
 *                      You can execute again the function again with the parameters interchanged.
 *                      However you will lose the reference if the value is from the base or with
 *                      object if you intended to do an assymetric comparison.
 *                      Setting this to true will do make sure the reference is not lost.
 * @returns           - The returned object will label the result of the comparison with the
 *                      value from base and with object.
 */
const diffSym = (baseObj, withObj, invert = false) => transform(baseObj, (result, value, key) => {
  if (isEqual(value, withObj[key])
    && has(withObj, key)) {
    return;
  }

  if (isObject(value)
    && isObject(withObj[key])
    && !Array.isArray(value)) {
    result[key] = diffSym(value, withObj[key], invert);
    return;
  }

  if (!invert) {
    result[key] = {
      base: value,
      with: withObj[key],
    };
    return;
  }

  if (invert) {
    result[key] = {
      base: withObj[key],
      with: value,
    };
  }
});

/**
 * Perform a assymmetric comparison on JSON object.
 * @param {*} baseObj - The base object to be used for comparison against the withObj.
 * @param {*} withObj - The withObject parameter is used as the comparison on the base object.
 * @returns           - The returned object will label the values with
 *                      reference to the base and with object.
 */
const diffJSON = (baseObj, withObj) => {
  // Deep clone the objects so we don't update the reference objects.
  const baseObjClone = JSON.parse(JSON.stringify(baseObj));
  const withObjClone = JSON.parse(JSON.stringify(withObj));

  const beforeDelta = diffSym(baseObjClone, withObjClone);
  const afterDelta = diffSym(withObjClone, baseObjClone, true);

  return merge(afterDelta, beforeDelta);
};

// By Example:

const beforeDataObj = {
  a: 1,
  c: { d: 2, f: 3 },
  g: 4,
  h: 5,
};
const afterDataObj = {
  a: 2,
  b: 3,
  c: { d: 1, e: 1 },
  h: 5,
};

const delta = diffJSON(beforeDataObj, afterDataObj);

// Assert expected result.
assert(isEqual(delta, {
  a: { base: 1, with: 2 },
  b: { base: undefined, with: 3 },
  c: {
    d: { base: 2, with: 1 },
    e: { base: undefined, with: 1 },
    f: { base: 3, with: undefined },
  },
  g: { base: 4, with: undefined },
}));

如果你只需要键比较:

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

在没有使用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;
}