比较JavaScript

时间:2015-10-16 17:04:51

标签: javascript json

我正在为一个大型应用程序使用angular-translate。有几个人提交代码+翻译,很多时候翻译对象不同步。

我正在构建一个Grunt插件来查看这两个文件'结构并比较它(只是键和整体结构,而不是值)。

主要目标是:

  • 查看每个文件,并检查整个对象的结构 (或文件,在这种情况下)与翻译的完全相同;
  • 如果出错,请返回不匹配的密钥。

事实证明它比我预期的要复杂一点。所以我想我可以做类似的事情:

  1. 对象排序;
  2. 检查值包含的数据类型(因为它们是翻译,它只有字符串或嵌套对象)并将其存储在另一个对象中,使得键等于原始键,值将是string' String',或者是一个对象,如果它是一个对象。该对象包含子元素;
  3. 递归重复步骤1-2,直到整个对象被映射和排序;
  4. 对所有文件执行相同的操作
  5. 对所有内容进行字符串化和比较。
  6. 一个很小的例子是以下对象:

    {
      key1: 'cool',
      key2: 'cooler',
      keyWhatever: {
        anotherObject: {
          key1: 'better',
          keyX: 'awesome'
        },
        aObject: 'actually, it\'s a string'
      },
      aKey: 'more awesomeness'
    }
    

    将映射到:

    {
      aKey: 'String',
      key1: 'String',
      key2: 'String',
      keyWhatever: {
        aObject: 'String',
        anotherObject: {
          key1: 'String',
          keyX: 'String'
        }
      }
    }
    

    在此之后,我会对所有对象进行字符串化并进行严格的比较。

    我的问题是,有更好的方法来执行此操作吗?在简单性和性能方面,因为有很多翻译文件,而且它们相当大。

    我试图找到已经这样做的图书馆,但我找不到。

    谢谢

    编辑感谢Jared指出对象无法排序。我很惭愧这样说:D另一个解决方案可能是迭代主翻译文件中的每个属性,如果它们是字符串,则将密钥与其他文件进行比较。如果它们是对象,"输入"他们,并做同样的事情。也许它比我的第一次猜测更简单。该怎么办?

3 个答案:

答案 0 :(得分:2)

假设你有两个JSON对象,jsonA和jsonB。

function compareValues(a, b) {

    //if a and b aren't the same type, they can't be equal
    if (typeof a !== typeof b) {
        return false;
    }

    if (typeof a === 'object') {
        var keysA = Object.keys(a).sort(),
            keysB = Object.keys(b).sort();

        //if a and b are objects with different no of keys, unequal
        if (keysA.length !== keysB.length) {
            return false;
        }

        //if keys aren't all the same, unequal
        if (!keysA.every(function(k, i) { return k === keysB[i];})) {
            return false;
        }

        //recurse on the values for each key
        return keysA.every(function(key) {
            //if we made it here, they have identical keys
            return compareValues(a[key], b[key]);
        });

    //for primitives just use a straight up check
    } else {
        return a === b;
    }
}

//true if their structure, values, and keys are identical    
var passed = compareValues(jsonA, jsonB); 

请注意,对于深层嵌套的JSON对象,这可能会溢出堆栈。另请注意,这将适用于JSON,但不一定是常规JS对象,因为日期对象,正则表达式等需要特殊处理。

答案 1 :(得分:1)

实际上,您确实需要对键进行排序,因为它们不需要以任何特定顺序吐出。写一个函数,

function getComparableForObject(obj) {
    var keys = Object.keys(obj);
    keys.sort(a, b => a > b ? 1 : -1);

    var comparable = keys.map(
            key => key + ":" + getValueRepresentation(obj[key])
        ).join(",");
    return "{" + comparable + "}";
}

其中getValueRepresentation是一个返回“String”或调用getComparableForObject的函数。如果您担心循环引用,请将Symbol添加到外部作用域repr,在上面的函数中指定obj[repr] = comparable,并在getValueRepresentation中检查已定义的每个对象obj[repr]并返回它而不是递归处理它。

答案 2 :(得分:1)

从对象中对键的数组进行排序。但是,排序的平均time complexityO(n⋅log(n))。我们可以做得更好。确保两组 A B 的快速通用算法等价如下:

for item in B
  if item in A
    remove item from A
  else
    sets are not equivalent
sets are equivalent iff A is empty

为了解决@ Katana31,我们可以通过维护一组访问对象并确保该对象的所有后代都不在列表中来检测循环引用:

# poorly written pseudo-code
fn detectCycles(A, found = {})
  if A in found
    there is a cycle
  else
    found = clone(found)
    add A to found
    for child in A
      detectCycles(child, found)

这是一个完整的实现(您可以找到一个假定JSON /非循环输入here的简化版本):

var hasOwn = Object.prototype.hasOwnProperty;
var indexOf = Array.prototype.indexOf;

function isObjectEmpty(obj) {
  for (var key in obj) {
    return false;
  }

  return true;
}

function copyKeys(obj) {
  var newObj = {};

  for (var key in obj) {
    newObj[key] = undefined;
  }

  return newObj;
}

// compares the structure of arbitrary values
function compareObjectStructure(a, b) {
  return function innerCompare(a, b, pathA, pathB) {
    if (typeof a !== typeof b) {
      return false;
    }

    if (typeof a === 'object') {
      // both or neither, but not mismatched
      if (Array.isArray(a) !== Array.isArray(b)) {
        return false;
      }

      if (indexOf.call(pathA, a) !== -1 || indexOf.call(pathB, b) !== -1) {
        return false;
      }

      pathA = pathA.slice();
      pathA.push(a);

      pathB = pathB.slice();
      pathB.push(b);

      if (Array.isArray(a)) {
        // can't compare structure in array if we don't have items in both
        if (!a.length || !b.length) {
          return true;
        }

        for (var i = 1; i < a.length; i++) {
          if (!innerCompare(a[0], a[i], pathA, pathA)) {
            return false;
          }
        }

        for (var i = 0; i < b.length; i++) {
          if (!innerCompare(a[0], b[i], pathA, pathB)) {
            return false;
          }
        }

        return true;
      }

      var map = copyKeys(a), keys = Object.keys(b);

      for (var i = 0; i < keys.length; i++) {
        var key = keys[i];

        if (!hasOwn.call(map, key) || !innerCompare(a[key], b[key], pathA,
            pathB)) {
          return false;
        }

        delete map[key];
      }

      // we should've found all the keys in the map
      return isObjectEmpty(map);
    }

    return true;
  }(a, b, [], []);
}

请注意,此实现直接比较两个对象的结构等效性,但不会将对象减少为直接可比较的值(如字符串)。我没有做任何性能测试,但我怀疑它不会增加重要的价值,虽然它将消除重复确保对象是非循环的需要。因此,您可以轻松地将compareObjectStructure拆分为两个函数 - 一个用于比较结构,另一个用于检查周期。