I think compare "JSON.Stringify" method doesn't correct;
Next Variant with checking recursive objects also:
interface IComparator<T> {
equals(obj: T, other: T): boolean;
}
export default class Comparator<T> implements IComparator<T> {
equals(item: T, otherItem: T): boolean {
if (typeof item !== typeof otherItem) {
return false;
}
const objectCache: any[] = [];
const otherObjectCache: any[] = [];
const getIndexFromCache = (compareObject: any, cacheArray: any[]): number =>
cacheArray.findIndex((item) => item === compareObject);
switch (true) {
case item === otherItem:
return true;
case typeof item !== 'object':
return item === otherItem;
case item === null || otherItem === null:
return item === null && otherItem === null;
case Object.keys(item).length !== Object.keys(otherItem).length:
return false;
default:
const object = item as any;
const otherObject = otherItem as any;
return Object.keys(object).every((key: string) => {
const hasKeyInOtherObject = Object.prototype.hasOwnProperty.call(otherItem, key);
if (!hasKeyInOtherObject) {
return false;
}
const cacheObjectIndex = getIndexFromCache(object[key], objectCache);
const cacheOtherObjectIndex = getIndexFromCache(otherObject[key], otherObjectCache);
if (cacheObjectIndex !== cacheOtherObjectIndex) {
return false;
}
const isEqualsCacheObjects =
cacheObjectIndex !== -1 && cacheOtherObjectIndex !== -1 && cacheObjectIndex === cacheOtherObjectIndex;
if (isEqualsCacheObjects) {
return true;
}
objectCache.push(object[key]);
otherObjectCache.push(otherObject[key]);
return new Comparator<any>().equals(object[key], otherObject[key]);
});
}
}
}
and test with jest for it:
import Comparator from './Comparator';
export default describe('Comparator', () => {
const recursiveA: { value: number; b?: any } = { value: 1 };
const recursiveB: { value: number; a?: any } = { value: 2 };
recursiveA.b = recursiveB;
recursiveB.a = recursiveA;
it.each([
[null, null, true],
[undefined, undefined, true],
[1, 1, true],
['test', 'test', true],
[[1, 2], [1, 2], true],
[{ a: 1, b: 3 }, { a: 1, b: 3 }, true],
[2, 1, false],
['test', 'test2', false],
[[1, 2], [2, 1], false],
[{ a: 1, b: 3 }, { a: 3, b: 1 }, false],
[recursiveA, recursiveB, false],
[null, 1, false],
[null, {}, false],
[undefined, null, false],
[1, '1', false],
])('compares values %o and %o are equal: %s', (value1: any, value2: any, expectedResult: boolean) => {
const comparator = new Comparator<any>();
const actualResult = comparator.equals(value1, value2);
expect<boolean>(actualResult).toBe(expectedResult);
});
});
And Javascript version:
export default class Comparator {
equals(item, otherItem) {
if (typeof item !== typeof otherItem) {
return false;
}
const objectCache = [];
const otherObjectCache = [];
const getIndexFromCache = (compareObject, cacheArray) => cacheArray.findIndex((item) => item === compareObject);
switch (true) {
case item === otherItem:
return true;
case typeof item !== 'object':
return item === otherItem;
case item === null || otherItem === null:
return item === null && otherItem === null;
case Object.keys(item).length !== Object.keys(otherItem).length:
return false;
default:
const object = item;
const otherObject = otherItem;
return Object.keys(object).every((key) => {
const hasKeyInOtherObject = Object.prototype.hasOwnProperty.call(otherItem, key);
if (!hasKeyInOtherObject) {
return false;
}
const cacheObjectIndex = getIndexFromCache(object[key], objectCache);
const cacheOtherObjectIndex = getIndexFromCache(otherObject[key], otherObjectCache);
if (cacheObjectIndex !== cacheOtherObjectIndex) {
return false;
}
const isEqualsCacheObjects = cacheObjectIndex !== -1 && cacheOtherObjectIndex !== -1 && cacheObjectIndex === cacheOtherObjectIndex;
if (isEqualsCacheObjects) {
return true;
}
objectCache.push(object[key]);
otherObjectCache.push(otherObject[key]);
return new Comparator().equals(object[key], otherObject[key]);
});
}
}
}