0

How to check if array contains different values with React.js and typescript?

Example:

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

I want to return false if all objects in array are same, and true if there is at least one different object.

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
  • What the code example shows is neither related to typescript nor to reactjs. – Peter Seliger Feb 11 '21 at 13:23
  • For any approach that e.g. takes the first array item and then does a comparison to all the other items one needs to somehow know the object-structure of such items. If it is as simple as shown above, it can be solved with a less complex object comparison than one usually needs in order to generically cover more complex ones. – Peter Seliger Feb 11 '21 at 13:27
  • Does this answer your question? [How to find duplicate values in a JavaScript array of objects, and output only unique values?](https://stackoverflow.com/questions/39885893/how-to-find-duplicate-values-in-a-javascript-array-of-objects-and-output-only-u) – Heretic Monkey Feb 11 '21 at 13:37
  • See also [In JavaScript, how do I check if an array has duplicate multiple values?](https://stackoverflow.com/q/59437252/215552) – Heretic Monkey Feb 11 '21 at 13:40
  • @nlstmn ... I contributed another approach which covers the comparison of JSON-conform JS-object in the most generic way. – Peter Seliger Feb 12 '21 at 09:20

4 Answers4

2

You can't use a direct equality comparison since objects will never return equal.

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

So firstly you have to decide what you're going to define as 'equal' for these objects.

Let's say for the sake of this that you use just the name field as the test for equality. So if two objects in the array have the same name field, then you'll call them equal. Then you'd define the function:

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

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

Of course you can change this function to reflect whatever you define as 'equal'. There are npm packages to help you with deep equality checks too, or you can JSON.stringify both and check that equality

Then you can use Array.some(). Array.some() will return true if any element in the array passes a test. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some

Testing if any element is not equal to the first should be sufficient.

const areNotAllEqual = yourArray.some((currentElement) => {
  return !areEqual(currentElement, yourArray[0])
})
Matt Wills
  • 676
  • 6
  • 11
2

After having commented on and criticized especially the approaches based on JSON.stringify, I want to contribute something on that matter. Since meanwhile all modern JS engines seem to be aware of an object's key order (in how this object was created) and also seem to guarantee such an order for key-iteration one could write a recursive function, which for any deeply nested but JSON-conform JS-objects reestablishes a normalized key-order for such objects but leaves arrays untouched.

Passing such key-normalized objects to JSON.stringify then makes such objects comparable by their's stringified signature ...

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

Applying the above to an approach which solves the OP's original problem in a more generic way then might look similar to the next provided lines ...

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; }
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
-1

I'd check whether stringified object array includes stringified item by referencing to a copied array where I remove the latest item. I'll use Array.every() to compare if all the items match together and then return the opposite value.

However, this can be very heavy operation if an object array is very lengthy

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));
Samuli Hakoniemi
  • 18,740
  • 1
  • 61
  • 74
  • 2
    The only thing one can say for sure is, that this approach works for the above example and also for the OP's example. But it already fails for a slightly change to the latter, like comparing e.g. `{ name: 'John', value: 1 }` to `{ value: 1, name: 'John' }`. – Peter Seliger Feb 11 '21 at 13:32
-1

This is not exactly react specific, but to check for differences you can iterate through the array using every like so.

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) {
    // https://stackoverflow.com/questions/1068834/object-comparison-in-javascript
    // 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)
ParthianShotgun
  • 602
  • 4
  • 20
  • 1
    If I change one of the objects to `{ value: 1, name: 'John' }` this will fail. – Heretic Monkey Feb 11 '21 at 13:35
  • 1
    Any approach based on `JSON.stringify` does fail for the slightest change to an objects key order. Thus the comparison result of e.g. `{ name: 'John', value: 1 }` to `{ value: 1, name: 'John' }` will fool one for it *says* `false` even though it should be `true` – Peter Seliger Feb 11 '21 at 13:37
  • @PeterSeliger I added a workaround to get over the positioning issue. I up-voted Matt's answer as I think that is the most "not hacky". I did something that I intuited myself, for the sake of having a different approach. – ParthianShotgun Feb 11 '21 at 14:10
  • I consider the update which does split and reassemble JSON-strings more a hack than a solution for it covers exactly the first level of a JSON conform object, but for a newbie might lead to the false believe it could do more. – Peter Seliger Feb 11 '21 at 16:09
  • @PeterSeliger Actually my intuition about its apparent hackiness betrayed me, I updated to show that nested objects do in fact work – ParthianShotgun Feb 11 '21 at 16:16
  • @ParthianShotgun ... please have a look at `{ name: 'John', value: 2, nest: { isValid: [1, 1] } }` and compare it to `{ name: 'John', value: 1, nest: { isValid: [2, 1] } }`. Both are distinct objects; they do not equal by any means. And yet both will return the same signature from the string blending and sorting approach. It is a hack, even worse than what I thought it was before, where I was giving credit to an approach that splits entries at commas, sorting and joining them again. – Peter Seliger Feb 11 '21 at 16:54
  • 1
    @PeterSeliger ahh, I see, this was what I was missing. Now that you said it, it makes perfect sense, my sorting is stripping away the meaning from objects and just puts all the data in the same row, definitely not a sustainable solution. – ParthianShotgun Feb 11 '21 at 19:53