对于 JSON 符合对象字面量的数组,如何比较这些项的相等性?

For an array of JSON conform object literals, how does one compare the equality of such items?

如何使用 React.js 和 typescript 检查数组是否包含不同的值?

示例:

[{
  name: 'John',
  value: 1,
}, {
  name: 'John',
  value: 1,
}, {
  name: 'Carla',
  value: 15,
}]

我想 return 如果数组中的所有对象都相同则为 false,如果至少有一个不同的对象则为 true。

我将通过引用删除最新项目的复制数组来检查字符串化对象数组是否包含字符串化项目。我将使用 Array.every() 比较所有项目是否匹配,然后 return 相反的值。

但是,如果对象数组很长,这可能是非常繁重的操作

const arrSame = [{name: 1}, {name: 1}, {name: 1}];
const arrDiff = [{name:1}, {name: 2}, {name: 2}];
const arrDiff2 = [{name:1}, {name: 1}, {name: 2}];

const hasDifferentValues = (arr) => !arr.every((item, i, ref) => JSON.stringify([...ref].shift()).includes(JSON.stringify(item)));


console.log(hasDifferentValues(arrSame));
console.log(hasDifferentValues(arrDiff));
console.log(hasDifferentValues(arrDiff2));

这不完全是特定于 React 的,但要检查差异,您可以像这样使用 every 遍历数组。

const fooArray = [{
    name: 'John',
    value: 1,
    nest: {
      isValid: [1, 2]
    }
  },
  {
    value: 1,
    name: 'John',
    nest: {
      isValid: [1, 1]
    }
  }, {
    name: 'John',
    value: 1,
    nest: {
      isValid: [1, 1]
    }
  }
]

// check each member against the last, see if there is a diff
const isSame = (element, index, arr) => {
  if (index > 0) {
    // 
    // return JSON.stringify(element) === JSON.stringify(arr[index - 1])
    // alternatively, you can check to see if some of the values are different
    // by stringifying and checking if either are permuations of each other
    // this is probably not the ideal way, but I added it for the sake of a different solution
    const currentObStr = JSON.stringify(element).split("").sort().join()
    const prevObStr = JSON.stringify(arr[index - 1]).split("").sort().join()
    return currentObStr === prevObStr
  }
  return true
}

const everyElementIsSame = fooArray.every(isSame)
console.log(everyElementIsSame)

您不能使用直接相等比较,因为对象永远不会 return 相等。

{} != {}{name: 'John', value: 1}!={name: 'John', value: 1}.

所以首先你必须决定你要为这些对象定义什么'equal'。

假设您仅使用 name 字段作为相等性测试。因此,如果数组中的两个对象具有相同的 name 字段,那么您将称它们相等。然后你定义函数:

type NameValue = {name: string, value: string}

const areEqual = (obj1: NameValue, obj2: NameValue): boolean => obj1.name === obj2.name

当然,您可以更改此函数以反映您定义为 'equal' 的任何内容。也有 npm 包可以帮助您进行深度相等性检查,或者您可以 JSON.stringify 两者并检查相等性

那么就可以使用Array.some()了。如果数组中的任何元素通过测试,Array.some() 将 return 为真。 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some

测试是否有任何元素不等于第一个元素就足够了。

const areNotAllEqual = yourArray.some((currentElement) => {
  return !areEqual(currentElement, yourArray[0])
})

在对基于 JSON.stringify 的方法进行评论和批评之后,我想就此做出一些贡献。由于同时所有现代 JS 引擎似乎都知道一个对象的键顺序(这个对象是如何创建的)并且似乎也保证了这样的键迭代顺序,所以可以编写一个递归函数,对于任何深度嵌套但 JSON-conform JS-objects 为此类对象重新建立规范化的键顺序,但保持数组不变。

将此类 key-normalized 对象传递给 JSON.stringify,然后通过其字符串化签名使此类对象具有可比性...

function defaultCompare(a, b) {
  return ((a < b) && -1) || ((a > b) && 1) || 0;
}
function comparePropertyNames(a, b) {
  return a.localeCompare
    ? a.localeCompare(b)
    : defaultCompare(a, b);
}

function getJsonDataWithNormalizedKeyOrder(data) {
  let value;
  if (Array.isArray(data)) {

    value = data.map(getJsonDataWithNormalizedKeyOrder);

  } else if (data && (typeof data === 'object')) {

    value = Object
      .getOwnPropertyNames(data)
      .sort(comparePropertyNames)
      .reduce((obj, key) => {
        obj[key] = getJsonDataWithNormalizedKeyOrder(data[key])
        return obj;
      }, {});

  } else {

    value = data;
  }
  return value;
}


const objA = {
  name: 'foo',
  value: 1,
  obj: {
    z: 'z',
    y: 'y',
    a: {
      name: 'bar',
      value: 2,
      obj: {
        x: 'x',
        w: 'w',
        b: 'b',
      },
      arr: ['3', 4, 'W', 'X', {
        name: 'baz',
        value: 3,
        obj: {
          k: 'k',
          i: 'i',
          c: 'c',
        },
        arr: ['5', 6, 'B', 'A'],
      }],
    },
  },
  arr: ['Z', 'Y', 1, '2'],
};

const objB = {
  arr: ['Z', 'Y', 1, '2'],
  obj: {
    z: 'z',
    y: 'y',
    a: {
      obj: {
        x: 'x',
        w: 'w',
        b: 'b',
      },
      arr: ['3', 4, 'W', 'X', {
        obj: {
          k: 'k',
          i: 'i',
          c: 'c',
        },
        name: 'baz',
        value: 3,
        arr: ['5', 6, 'B', 'A'],
      }],
      name: 'bar',
      value: 2,
    },
  },
  name: 'foo',
  value: 1,
};

const objC = {
  arr: ['Z', 'Y', 1, '2'],
  obj: {
    z: 'z',
    y: 'y',
    a: {
      obj: {
        x: 'x',
        w: 'w',
        b: 'b',
      },
      arr: ['3', 4, 'W', 'X', {
        obj: {
          k: 'k',
          i: 'i',
          c: 'c',
        },
        name: 'baz',
        value: 3,
        arr: ['5', 6, 'B', 'A'],
      }],
      name: 'bar',
      value: 2,
    },
  },
  name: 'foo',
  value: 2,
};


console.log(
  'getJsonDataWithNormalizedKeyOrder(objA) ...',
  getJsonDataWithNormalizedKeyOrder(objA)
);
console.log(
  'getJsonDataWithNormalizedKeyOrder(objB) ...',
  getJsonDataWithNormalizedKeyOrder(objB)
);

console.log(
  'JSON.stringify(getJsonDataWithNormalizedKeyOrder(objA)) ...',
  JSON.stringify(getJsonDataWithNormalizedKeyOrder(objA))
);
console.log(
  'JSON.stringify(getJsonDataWithNormalizedKeyOrder(objB)) ...',
  JSON.stringify(getJsonDataWithNormalizedKeyOrder(objB))
);
console.log(
  'JSON.stringify(getJsonDataWithNormalizedKeyOrder(objC)) ...',
  JSON.stringify(getJsonDataWithNormalizedKeyOrder(objC))
);

console.log(
  'JSON.stringify(getJsonDataWithNormalizedKeyOrder(objA)).length ...',
  JSON.stringify(getJsonDataWithNormalizedKeyOrder(objA)).length
);
console.log(
  'JSON.stringify(getJsonDataWithNormalizedKeyOrder(objB)).length ...',
  JSON.stringify(getJsonDataWithNormalizedKeyOrder(objB)).length
);
console.log(
  'JSON.stringify(getJsonDataWithNormalizedKeyOrder(objC)).length ...',
  JSON.stringify(getJsonDataWithNormalizedKeyOrder(objC)).length
);

console.log(`
  JSON.stringify(
    getJsonDataWithNormalizedKeyOrder(objA)
  ) === JSON.stringify(
    getJsonDataWithNormalizedKeyOrder(objB)
  ) ?`,
  JSON.stringify(
    getJsonDataWithNormalizedKeyOrder(objA)
  ) === JSON.stringify(
    getJsonDataWithNormalizedKeyOrder(objB)
  )
);
console.log(`
  JSON.stringify(
    getJsonDataWithNormalizedKeyOrder(objA)
  ) === JSON.stringify(
    getJsonDataWithNormalizedKeyOrder(objC)
  ) ?`,
  JSON.stringify(
    getJsonDataWithNormalizedKeyOrder(objA)
  ) === JSON.stringify(
    getJsonDataWithNormalizedKeyOrder(objC)
  )
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

将以上内容应用于以更通用的方式解决 OP 的原始问题的方法,然后可能看起来类似于下一个提供的行...

function defaultCompare(a, b) {
  return ((a < b) && -1) || ((a > b) && 1) || 0;
}
function comparePropertyNames(a, b) {
  return a.localeCompare
    ? a.localeCompare(b)
    : defaultCompare(a, b);
}

function getJsonDataWithNormalizedKeyOrder(data) {
  let value;
  if (Array.isArray(data)) {

    value = data.map(getJsonDataWithNormalizedKeyOrder);

  } else if (data && (typeof data === 'object')) {

    value = Object
      .getOwnPropertyNames(data)
      .sort(comparePropertyNames)
      .reduce((obj, key) => {
        obj[key] = getJsonDataWithNormalizedKeyOrder(data[key])
        return obj;
      }, {});

  } else {

    value = data;
  }
  return value;
}


const sampleList = [{
  name: 'John',
  value: 1,
}, {
  value: 1,
  name: 'John',
}, {
  name: 'Carla',
  value: 15,
}];

function hasDifferentValues(arr) {
  // stringified first item reference.
  const referenceItem = JSON.stringify(getJsonDataWithNormalizedKeyOrder(arr[0]));

  // run `some` from a sub-array which excludes the original array's first item.
  return arr.slice(1).some(item =>
    referenceItem !== JSON.stringify(getJsonDataWithNormalizedKeyOrder(item))
  );
}

console.log(
  'hasDifferentValues(sampleList) ?',
  hasDifferentValues(sampleList)
);

console.log(
  'hasDifferentValues(sampleList.slice(0,2)) ?',
  hasDifferentValues(sampleList.slice(0,2))
);
console.log(
  'hasDifferentValues(sampleList.slice(0,1)) ?',
  hasDifferentValues(sampleList.slice(0,1))
);

console.log(
  'hasDifferentValues(sampleList.slice(1)) ?',
  hasDifferentValues(sampleList.slice(1))
);
.as-console-wrapper { min-height: 100%!important; top: 0; }