Files
twenty/packages/twenty-shared/src/utils/json/fast-deep-equal.ts
T
Thomas Trompette ac89b5aff6 Improve object record changed performances (#16398)
Using deepEqual, average over 3 calls:
- version trigger 1.53ms
- version steps 38.3ms
- run state 372.8 ms

Using stringified hash, average over 3 calls:
- version trigger 0.07ms
- version steps 0.74 ms
- run state 0.521ms

Short fastDeepEqual
- version trigger 0.028ms
- version steps 0.197ms
- run state 0.214ms

Lib fastDeepEqual
- version trigger 0.038ms
- version steps 0.187ms
- run state 0.230ms
2025-12-08 17:06:53 +01:00

113 lines
3.1 KiB
TypeScript

// Source: https://github.com/epoberezkin/fast-deep-equal/blob/master/src/index.jst
export const fastDeepEqual = (
firstValue: unknown,
secondValue: unknown,
): boolean => {
if (firstValue === secondValue) return true;
if (
firstValue &&
secondValue &&
typeof firstValue === 'object' &&
typeof secondValue === 'object'
) {
if (firstValue.constructor !== secondValue.constructor) return false;
if (Array.isArray(firstValue)) {
const firstArray = firstValue;
const secondArray = secondValue as unknown[];
if (firstArray.length !== secondArray.length) return false;
for (let index = firstArray.length; index-- !== 0; ) {
if (!fastDeepEqual(firstArray[index], secondArray[index])) return false;
}
return true;
}
if (firstValue instanceof Map && secondValue instanceof Map) {
if (firstValue.size !== secondValue.size) return false;
for (const [key] of firstValue.entries()) {
if (!secondValue.has(key)) return false;
}
for (const [key, value] of firstValue.entries()) {
if (!fastDeepEqual(value, secondValue.get(key))) return false;
}
return true;
}
if (firstValue instanceof Set && secondValue instanceof Set) {
if (firstValue.size !== secondValue.size) return false;
for (const item of firstValue) {
if (!secondValue.has(item)) return false;
}
return true;
}
if (ArrayBuffer.isView(firstValue) && ArrayBuffer.isView(secondValue)) {
const firstView = firstValue as unknown as ArrayLike<unknown>;
const secondView = secondValue as unknown as ArrayLike<unknown>;
if (firstView.length !== secondView.length) return false;
for (let index = firstView.length; index-- !== 0; ) {
if (firstView[index] !== secondView[index]) return false;
}
return true;
}
if (firstValue.constructor === RegExp) {
const firstRegex = firstValue as RegExp;
const secondRegex = secondValue as RegExp;
return (
firstRegex.source === secondRegex.source &&
firstRegex.flags === secondRegex.flags
);
}
if (firstValue.valueOf !== Object.prototype.valueOf) {
return firstValue.valueOf() === secondValue.valueOf();
}
if (firstValue.toString !== Object.prototype.toString) {
return firstValue.toString() === secondValue.toString();
}
const firstKeys = Object.keys(firstValue);
const secondKeys = Object.keys(secondValue);
if (firstKeys.length !== secondKeys.length) return false;
for (let index = firstKeys.length; index-- !== 0; ) {
if (!(firstKeys[index] in secondValue)) return false;
}
for (let index = firstKeys.length; index-- !== 0; ) {
const key = firstKeys[index];
if (
!fastDeepEqual(
(firstValue as Record<string, unknown>)[key],
(secondValue as Record<string, unknown>)[key],
)
) {
return false;
}
}
return true;
}
// true if both NaN, false otherwise
return firstValue !== firstValue && secondValue !== secondValue;
};