【问题标题】:Comparing Arrays of Objects in JavaScript比较 JavaScript 中的对象数组
【发布时间】:2008-08-25 22:15:21
【问题描述】:

我想比较 JavaScript 代码中的 2 个对象数组。对象总共有 8 个属性,但每个对象都不会有每个值,并且数组永远不会大于 8 个项目,所以可能是遍历每个对象然后查看值的蛮力方法8 个属性是做我想做的最简单的方法,但在实施之前,我想看看是否有人有更优雅的解决方案。有什么想法吗?

【问题讨论】:

    标签: javascript arrays comparison


    【解决方案1】:

    编辑:您不能在当前常见的基于浏览器的 JavaScript 解释器实现中重载运算符。

    要回答原始问题,您可以采用一种方法,请注意,这有点小技巧,只需 serialize the two arrays to JSON,然后比较两个 JSON 字符串。这只会告诉您数组是否不同,显然您也可以对数组中的 每个 对象执行此操作,以查看哪些不同。

    另一种选择是使用一个库,它有一些很好的工具来比较对象——我使用并推荐MochiKit


    编辑: The answer kamens gave 也值得考虑,因为用于比较两个给定对象的单个函数将比任何库小得多,以执行我的建议(尽管我的建议肯定会足够好)。

    这是一个简单的实现,可能对您来说已经足够了 - 请注意,此实现存在潜在问题:

    function objectsAreSame(x, y) {
       var objectsAreSame = true;
       for(var propertyName in x) {
          if(x[propertyName] !== y[propertyName]) {
             objectsAreSame = false;
             break;
          }
       }
       return objectsAreSame;
    }
    

    假设两个对象具有完全相同的属性列表。

    哦,很明显,无论好坏,我都属于唯一一个返回点阵营。 :)

    【讨论】:

    • 只是指出限制:这看起来在比较包含对象的对象时会失败。 (正如您所提到的,当两个对象没有“完全相同的属性列表”时,它将失败,因为y 被允许成为x 的超集。
    • 关于 JSON 序列化建议的一个警告是,如果您正在比较对象(不是数组)并且不关心顺序(例如命名键,而不是数字数组),那么 JSON 序列化不会工作。
    • @AlanH。你的意思是JSON.stringify 应该适用于比较两个非对象(例如数字、字符串)数组以及两个对象数组,但不适用于比较两个对象?如果是这样,为什么?尤其是在比较两个Object数组的情况下?
    • 忘记对象数组和对象数组,不管这意味着什么。想想{a: 1, b: 1}{b: 1, a: 1}。我们不关心对象的顺序,但这些字符串显然是不同的。
    【解决方案2】:

    由于序列化通常不起作用(仅当属性的顺序匹配时:JSON.stringify({a:1,b:2}) !== JSON.stringify({b:2,a:1})),您必须检查属性的数量并比较每个属性:

    const objectsEqual = (o1, o2) =>
        Object.keys(o1).length === Object.keys(o2).length 
            && Object.keys(o1).every(p => o1[p] === o2[p]);
    
    const obj1 = { name: 'John', age: 33};
    const obj2 = { age: 33, name: 'John' };
    const obj3 = { name: 'John', age: 45 };
            
    console.log(objectsEqual(obj1, obj2)); // true
    console.log(objectsEqual(obj1, obj3)); // false

    如果需要深度比较,可以递归调用函数:

    const obj1 = { name: 'John', age: 33, info: { married: true, hobbies: ['sport', 'art'] } };
    const obj2 = { age: 33, name: 'John', info: { hobbies: ['sport', 'art'], married: true } };
    const obj3 = { name: 'John', age: 33 };
    
    const objectsEqual = (o1, o2) => 
        typeof o1 === 'object' && Object.keys(o1).length > 0 
            ? Object.keys(o1).length === Object.keys(o2).length 
                && Object.keys(o1).every(p => objectsEqual(o1[p], o2[p]))
            : o1 === o2;
            
    console.log(objectsEqual(obj1, obj2)); // true
    console.log(objectsEqual(obj1, obj3)); // false

    那么就很容易用这个函数来比较数组中的对象了:

    const arr1 = [obj1, obj1];
    const arr2 = [obj1, obj2];
    const arr3 = [obj1, obj3];
    
    const arraysEqual = (a1, a2) => 
       a1.length === a2.length && a1.every((o, idx) => objectsEqual(o, a2[idx]));
    
    console.log(arraysEqual(arr1, arr2)); // true
    console.log(arraysEqual(arr1, arr3)); // false
    

    【讨论】:

    • 最佳答案。简洁而完美。应该在上面。
    • 太棒了。只需将深度比较与验证包装起来即可使用 null 属性,否则返回 o1 === o2。酷。
    • 不考虑顺序。不适用于以下场景 const arr1 = [obj1, obj2];常量 arr2 = [obj2, obj1];
    • 最佳答案,为我节省了大量时间。谢谢!
    • @Cels 这是正确的行为,[1,2] 和 [2,1] 相等。
    【解决方案3】:

    老实说,每个对象最多有 8 个对象和 8 个属性,最好的办法是遍历每个对象并直接进行比较。它会很快而且很容易。

    如果您要经常使用这些类型的比较,那么我同意 Jason 关于 JSON 序列化的观点……但否则没有必要使用新的库或 JSON 序列化代码来减慢您的应用程序的速度。

    【讨论】:

    • "...我同意 Jason 关于 JSON..." +1 就是为了这个! ;-)
    【解决方案4】:

    我知道这是一个老问题,提供的答案工作正常......但这有点短,不需要任何额外的库(即 JSON):

    function arraysAreEqual(ary1,ary2){
      return (ary1.join('') == ary2.join(''));
    }
    

    【讨论】:

    • OP 想要加入对象数组。这仅适用于标量数组。
    • 它也很脆弱。如果:a=["1,2"] , b=["1", "2"] 那么两个不同数组上的join() 将导致'1,2'
    • @Jason Moore 不正确,a.join('') // => "1,2"; b.join('') // => "12"
    • 你对那个特定的例子是正确的,但它仍然很脆弱。 a=["12"], b=["1", "2"] 导致 "12"=="12" 并且我认为没有任何分隔符可以拯救你,因为它可能在 obj 本身中。长度检查也无法修复它,因为a=["12", "3"], b=["1", "23"]
    • 一个更健壮的实现:return ary1.join(',') === ary2.join(',');
    【解决方案5】:

    我研究了一个简单的算法来比较两个对象的内容并返回一个可理解的差异列表。以为我会分享。它借鉴了jQuery的一些想法,即map函数实现以及对象和数组类型检查。

    它返回一个“差异对象”列表,它们是具有差异信息的数组。很简单。

    这里是:

    // compare contents of two objects and return a list of differences
    // returns an array where each element is also an array in the form:
    // [accessor, diffType, leftValue, rightValue ]
    //
    // diffType is one of the following:
    //   value: when primitive values at that index are different
    //   undefined: when values in that index exist in one object but don't in 
    //              another; one of the values is always undefined
    //   null: when a value in that index is null or undefined; values are
    //         expressed as boolean values, indicated wheter they were nulls
    //   type: when values in that index are of different types; values are 
    //         expressed as types
    //   length: when arrays in that index are of different length; values are
    //           the lengths of the arrays
    //
    
    function DiffObjects(o1, o2) {
        // choose a map() impl.
        // you may use $.map from jQuery if you wish
        var map = Array.prototype.map?
            function(a) { return Array.prototype.map.apply(a, Array.prototype.slice.call(arguments, 1)); } :
            function(a, f) { 
                var ret = new Array(a.length), value;
                for ( var i = 0, length = a.length; i < length; i++ ) 
                    ret[i] = f(a[i], i);
                return ret.concat();
            };
    
        // shorthand for push impl.
        var push = Array.prototype.push;
    
        // check for null/undefined values
        if ((o1 == null) || (o2 == null)) {
            if (o1 != o2)
                return [["", "null", o1!=null, o2!=null]];
    
            return undefined; // both null
        }
        // compare types
        if ((o1.constructor != o2.constructor) ||
            (typeof o1 != typeof o2)) {
            return [["", "type", Object.prototype.toString.call(o1), Object.prototype.toString.call(o2) ]]; // different type
    
        }
    
        // compare arrays
        if (Object.prototype.toString.call(o1) == "[object Array]") {
            if (o1.length != o2.length) { 
                return [["", "length", o1.length, o2.length]]; // different length
            }
            var diff =[];
            for (var i=0; i<o1.length; i++) {
                // per element nested diff
                var innerDiff = DiffObjects(o1[i], o2[i]);
                if (innerDiff) { // o1[i] != o2[i]
                    // merge diff array into parent's while including parent object name ([i])
                    push.apply(diff, map(innerDiff, function(o, j) { o[0]="[" + i + "]" + o[0]; return o; }));
                }
            }
            // if any differences were found, return them
            if (diff.length)
                return diff;
            // return nothing if arrays equal
            return undefined;
        }
    
        // compare object trees
        if (Object.prototype.toString.call(o1) == "[object Object]") {
            var diff =[];
            // check all props in o1
            for (var prop in o1) {
                // the double check in o1 is because in V8 objects remember keys set to undefined 
                if ((typeof o2[prop] == "undefined") && (typeof o1[prop] != "undefined")) {
                    // prop exists in o1 but not in o2
                    diff.push(["[" + prop + "]", "undefined", o1[prop], undefined]); // prop exists in o1 but not in o2
    
                }
                else {
                    // per element nested diff
                    var innerDiff = DiffObjects(o1[prop], o2[prop]);
                    if (innerDiff) { // o1[prop] != o2[prop]
                        // merge diff array into parent's while including parent object name ([prop])
                        push.apply(diff, map(innerDiff, function(o, j) { o[0]="[" + prop + "]" + o[0]; return o; }));
                    }
    
                }
            }
            for (var prop in o2) {
                // the double check in o2 is because in V8 objects remember keys set to undefined 
                if ((typeof o1[prop] == "undefined") && (typeof o2[prop] != "undefined")) {
                    // prop exists in o2 but not in o1
                    diff.push(["[" + prop + "]", "undefined", undefined, o2[prop]]); // prop exists in o2 but not in o1
    
                }
            }
            // if any differences were found, return them
            if (diff.length)
                return diff;
            // return nothing if objects equal
            return undefined;
        }
        // if same type and not null or objects or arrays
        // perform primitive value comparison
        if (o1 != o2)
            return [["", "value", o1, o2]];
    
        // return nothing if values are equal
        return undefined;
    }
    

    【讨论】:

      【解决方案6】:

      我尝试了JSON.stringify() 并为我工作。

      let array1 = [1,2,{value:'alpha'}] , array2 = [{value:'alpha'},'music',3,4];
      
      JSON.stringify(array1) // "[1,2,{"value":"alpha"}]"
      
      JSON.stringify(array2) // "[{"value":"alpha"},"music",3,4]"
      
      JSON.stringify(array1) === JSON.stringify(array2); // false
      

      【讨论】:

      • 这里要注意——如果对象属性有问题,这将不起作用
      • 首先我们可以对数组进行排序,然后使用stringify
      • @Ehsansarshar 这不起作用...您需要对所有对象属性和数组进行排序...
      【解决方案7】:

      这是我的尝试,使用Node's assert module + npm 包object-hash

      我想您想检查两个数组是否包含相同的对象,即使这些对象在两个数组之间的排序不同。

      var assert = require('assert');
      var hash = require('object-hash');
      
      var obj1 = {a: 1, b: 2, c: 333},
          obj2 = {b: 2, a: 1, c: 444},
          obj3 = {b: "AAA", c: 555},
          obj4 = {c: 555, b: "AAA"};
      
      var array1 = [obj1, obj2, obj3, obj4];
      var array2 = [obj3, obj2, obj4, obj1]; // [obj3, obj3, obj2, obj1] should work as well
      
      // calling assert.deepEquals(array1, array2) at this point FAILS (throws an AssertionError)
      // even if array1 and array2 contain the same objects in different order,
      // because array1[0].c !== array2[0].c
      
      // sort objects in arrays by their hashes, so that if the arrays are identical,
      // their objects can be compared in the same order, one by one
      var array1 = sortArrayOnHash(array1);
      var array2 = sortArrayOnHash(array2);
      
      // then, this should output "PASS"
      try {
          assert.deepEqual(array1, array2);
          console.log("PASS");
      } catch (e) {
          console.log("FAIL");
          console.log(e);
      }
      
      // You could define as well something like Array.prototype.sortOnHash()...
      function sortArrayOnHash(array) {
          return array.sort(function(a, b) {
              return hash(a) > hash(b);
          });
      }
      

      【讨论】:

        【解决方案8】:

        当函数需要等于空数组时,有一个优化的代码(在这种情况下返回 false)

        const objectsEqual = (o1, o2) => {
            if (o2 === null && o1 !== null) return false;
            return o1 !== null && typeof o1 === 'object' && Object.keys(o1).length > 0 ?
                Object.keys(o1).length === Object.keys(o2).length && 
                Object.keys(o1).every(p => objectsEqual(o1[p], o2[p]))
                : (o1 !== null && Array.isArray(o1) && Array.isArray(o2) && !o1.length && 
                !o2.length) ? true : o1 === o2;
        }
        

        【讨论】:

        • 这是快速解决方案 ++rep
        【解决方案9】:

        我在排序、测试和工作方面的实践实现。

        const obj1 = { name: 'John', age: 33};
        const obj2 = { age: 33, name: 'John' };
        const obj3 = { name: 'John', age: 45 };
        
        const equalObjs = ( obj1, obj2 ) => {
        let keyExist = false;
        for ( const [key, value] of Object.entries(obj1) ) {
             // Search each key in reference object and attach a callback function to 
             // compare the two object keys
            if( Object.keys(obj2).some( ( e ) => e == key ) ) {
                keyExist = true;
            }
        }
        
        return keyExist;
        
        }
        
        
        console.info( equalObjs( obj1, obj2 ) );
        

        比较你的数组

        // Sort Arrays
            var arr1 = arr1.sort(( a, b ) => {
            var fa = Object.keys(a);
            var fb = Object.keys(b);
        
            if (fa < fb) {
                return -1;
            }
            if (fa > fb) {
                return 1;
            }
            return 0;
        });
        
        var arr2 = arr2.sort(( a, b ) => {
            var fa = Object.keys(a);
            var fb = Object.keys(b);
        
            if (fa < fb) {
                return -1;
            }
            if (fa > fb) {
                return 1;
            }
            return 0;
        });
        
        const equalArrays = ( arr1, arr2 ) => {
            // If the arrays are different length we an eliminate immediately
            if( arr1.length !== arr2.length ) {
                return false;
            } else if ( arr1.every(( obj, index ) => equalObjs( obj, arr2[index] ) ) ) {
                return true;
              } else { 
                return false;
              }
            }
        
            console.info( equalArrays( arr1, arr2 ) );
        

        【讨论】:

          【解决方案10】:

          请试试这个:

          function used_to_compare_two_arrays(a, b)
          {
            // This block will make the array of indexed that array b contains a elements
            var c = a.filter(function(value, index, obj) {
              return b.indexOf(value) > -1;
            });
          
            // This is used for making comparison that both have same length if no condition go wrong 
            if (c.length !== a.length) {
              return 0;
            } else{
              return 1;
            }
          }
          

          【讨论】:

          • 嗯... if 语句可以简化为 return c.length === a.length;
          【解决方案11】:

          @JasonBunting 的答案中提到的 objectsAreSame 函数对我来说很好用。但是,有一个小问题:如果x[propertyName]y[propertyName] 是对象(typeof x[propertyName] == 'object'),则需要递归调用函数来比较它们。

          【讨论】:

            【解决方案12】:

            我正在分享我的比较函数实现,因为它可能对其他人有帮助:

             /*
              null AND null // true
              undefined AND undefined // true
              null AND undefined // false
              [] AND [] // true
              [1, 2, 'test'] AND ['test', 2, 1] // true
              [1, 2, 'test'] AND ['test', 2, 3] // false
              [undefined, 2, 'test'] AND ['test', 2, 1] // false
              [undefined, 2, 'test'] AND ['test', 2, undefined] // true
              [[1, 2], 'test'] AND ['test', [2, 1]] // true
              [1, 'test'] AND ['test', [2, 1]] // false
              [[2, 1], 'test'] AND ['test', [2, 1]] // true
              [[2, 1], 'test'] AND ['test', [2, 3]] // false
              [[[3, 4], 2], 'test'] AND ['test', [2, [3, 4]]] // true
              [[[3, 4], 2], 'test'] AND ['test', [2, [5, 4]]] // false
              [{x: 1, y: 2}, 'test'] AND ['test', {x: 1, y: 2}] // true
              1 AND 1 // true
              {test: 1} AND ['test', 2, 1] // false
              {test: 1} AND {test: 1} // true
              {test: 1} AND {test: 2} // false
              {test: [1, 2]} AND {test: [1, 2]} // true
              {test: [1, 2]} AND {test: [1]} // false
              {test: [1, 2], x: 1} AND {test: [1, 2], x: 2} // false
              {test: [1, { z: 5 }], x: 1} AND {x: 1, test: [1, { z: 5}]} // true
              {test: [1, { z: 5 }], x: 1} AND {x: 1, test: [1, { z: 6}]} // false
               */
              function is_equal(x, y) {
                  const
                      arr1 = x,
                      arr2 = y,
                      is_objects_equal = function (obj_x, obj_y) {
                          if (!(
                              typeof obj_x === 'object' &&
                              Object.keys(obj_x).length > 0
                          ))
                              return obj_x === obj_y;
            
                          return Object.keys(obj_x).length === Object.keys(obj_y).length &&
                              Object.keys(obj_x).every(p => is_objects_equal(obj_x[p], obj_y[p]));
                      }
                      ;
            
                  if (!( Array.isArray(arr1) && Array.isArray(arr2) ))
                      return (
                          arr1 && typeof arr1 === 'object' &&
                          arr2 && typeof arr2 === 'object'
                      )
                          ? is_objects_equal(arr1, arr2)
                          : arr1 === arr2;
            
                  if (arr1.length !== arr2.length)
                      return false;
            
                  for (const idx_1 of arr1.keys())
                      for (const idx_2 of arr2.keys())
                          if (
                              (
                                  Array.isArray(arr1[idx_1]) &&
                                  this.is_equal(arr1[idx_1], arr2[idx_2])
                              ) ||
                              is_objects_equal(arr1[idx_1], arr2[idx_2])
                          )
                          {
                              arr2.splice(idx_2, 1);
                              break;
                          }
            
                  return !arr2.length;
              }

            【讨论】:

              【解决方案13】:

              使用来自lodash的_.somehttps://lodash.com/docs/4.17.11#some

              const array1AndArray2NotEqual = 
                        _.some(array1, (a1, idx) => a1.key1 !== array2[idx].key1 
                                                   || a1.key2 !== array2[idx].key2 
                                                   || a1.key3 !== array2[idx].key3);
              

              【讨论】:

                【解决方案14】:

                与 json 比较是非常糟糕的。试试这个包来比较嵌套数组并获得差异。

                https://www.npmjs.com/package/deep-object-diff

                【讨论】:

                  【解决方案15】:

                  这是我的解决方案。它将比较也有对象和数组的数组。元素可以停留在任何位置。 示例:

                  const array1 = [{a: 1}, {b: 2}, { c: 0, d: { e: 1, f: 2, } }, [1,2,3,54]];
                  const array2 = [{a: 1}, {b: 2}, { c: 0, d: { e: 1, f: 2, } }, [1,2,3,54]];
                  
                  const arraysCompare = (a1, a2) => {
                    if (a1.length !== a2.length) return false;
                    const objectIteration = (object) => {
                      const result = [];
                      const objectReduce = (obj) => {
                        for (let i in obj) {
                          if (typeof obj[i] !== 'object') {
                            result.push(`${i}${obj[i]}`);
                          } else {
                            objectReduce(obj[i]);
                          }
                        }
                      };
                      objectReduce(object);
                      return result;
                    };
                    const reduceArray1 = a1.map(item => {
                      if (typeof item !== 'object') return item;
                      return objectIteration(item).join('');
                    });
                    const reduceArray2 = a2.map(item => {
                      if (typeof item !== 'object') return item;
                      return objectIteration(item).join('');
                    });
                    const compare =  reduceArray1.map(item => reduceArray2.includes(item));
                    return compare.reduce((acc, item) => acc + Number(item)) === a1.length;
                  };
                  
                  console.log(arraysCompare(array1, array2));
                  

                  【讨论】:

                    【解决方案16】:

                    不确定性能...必须在大对象上进行测试..但是,这对我很有用..与其他解决方案相比,它的优势是,对象/数组不必在相同的顺序....

                    它实际上获取第一个数组中的第一个对象,并扫描第二个数组中的每个对象.. 如果匹配,它将继续到另一个

                    绝对有一种优化方法,但它正在工作:)

                    感谢@ttulka我从他的工作中得到启发......只是做了一点工作

                    const objectsEqual = (o1, o2) => {
                      let match = false
                        if(typeof o1 === 'object' && Object.keys(o1).length > 0) {
                         match = (Object.keys(o1).length === Object.keys(o2).length && Object.keys(o1).every(p => objectsEqual(o1[p], o2[p])))
                        }else {
                         match = (o1 === o2)
                        }
                        return match
                    }
                    
                    const arraysEqual = (a1, a2) => {
                      let finalMatch = []
                      let itemFound = []
                      
                      if(a1.length === a2.length) {
                        finalMatch = []
                        a1.forEach( i1 => {
                          itemFound = []
                          a2.forEach( i2 => { 
                            itemFound.push(objectsEqual(i1, i2)) 
                          })
                            finalMatch.push(itemFound.some( i => i === true))  
                        }) 
                      } 
                      return finalMatch.every(i => i === true)
                    }
                    
                    const ar1 = [
                      { id: 1, name: "Johnny", data: { body: "Some text"}},
                      { id: 2, name: "Jimmy"}
                    ]
                    const ar2 = [
                      {name: "Jimmy", id: 2},
                      {name: "Johnny", data: { body: "Some text"}, id: 1}
                    ]
                    
                    
                    console.log("Match:",arraysEqual(ar1, ar2))
                    

                    jsfiddle:https://jsfiddle.net/x1pubs6q/

                    或者只使用 lodash :))))

                    const _ = require('lodash')
                    
                    const isArrayEqual = (x, y) => {
                      return _.isEmpty(_.xorWith(x, y, _.isEqual));
                    };
                    

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2021-10-05
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2021-12-07
                      • 2018-05-15
                      相关资源
                      最近更新 更多