isEqual
The isEqual utility performs a deep equality comparison between two values. It determines if two values are structurally identical, recursively checking nested objects and arrays.
Implementation
View Source Code
ts
/**
* Deeply compares two values for equality, including objects, arrays, and primitives.
* Detects circular references and optimizes performance.
*
* @example
* ```ts
* isEqual([1, 2, 3], [1, 2, 3]); // true
* isEqual([1, 2], [1, 2, 3]); // false
* isEqual({ a: 1, b: 2 }, { a: 1, b: 2 }); // true
* isEqual({ a: { b: 2 } }, { a: { b: 2 } }); // true
* isEqual({ a: 1 }, { a: 2 }); // false
* isEqual({ a: 1 }, { b: 1 }); // false
* isEqual(new Date('2023-01-01'), new Date('2023-01-01')); // true
* isEqual(new Date('2023-01-01'), new Date('2023-01-02')); // false
* isEqual(new Date('2023-01-01'), 1); // false
* ```
*
* @param a - First value to compare.
* @param b - Second value to compare.
* @returns Whether the values are deeply equal.
*/
export function isEqual(a: unknown, b: unknown): boolean {
return safeIsEqual(a, b, new WeakMap());
}
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: -
function safeIsEqual(a: unknown, b: unknown, visited: WeakMap<object, object>): boolean {
// Check for strict equality (handles primitives and references)
if (a === b) return true;
// If either is null or not an object, they're not equal
if (a == null || b == null || typeof a !== typeof b || typeof a !== 'object' || typeof b !== 'object') return false;
// Check for circular references
// We only track 'a' because if 'a' is cyclical, 'b' must also be cyclic to be equal
if (visited.has(a as object)) {
return visited.get(a as object) === b;
}
visited.set(a as object, b as object);
// Array comparison
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) return false;
for (let idx = 0; idx < a.length; idx++) {
if (!safeIsEqual(a[idx], b[idx], visited)) return false;
}
return true;
}
// Ensure both are arrays or neither is
if (Array.isArray(a) !== Array.isArray(b)) return false;
// Date comparison
if (a instanceof Date && b instanceof Date) {
return a.getTime() === b.getTime();
}
// Object comparison
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
for (const key of keysA) {
if (
!Object.hasOwn(b, key) ||
!safeIsEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key], visited)
)
return false;
}
return true;
}
export const IS_EQUAL_ERROR_MSG = 'Expected two values to be equal';Features
- Isomorphic: Works in both Browser and Node.js.
- Deep Comparison: Correctly handles nested structures of any depth.
- Strict Equality: Uses strict equality (
===) for primitives. - Comprehensive Support: Properly compares
Date,RegExp, and other built-in objects.
API
ts
function isEqual(a: unknown, b: unknown): boolean;Parameters
a: The first value to compare.b: The second value to compare.
Returns
trueif the values are deeply equal; otherwise,false.
Examples
Comparing Objects
ts
import { isEqual } from '@vielzeug/toolkit';
const obj1 = { id: 1, meta: { tags: ['a', 'b'] } };
const obj2 = { id: 1, meta: { tags: ['a', 'b'] } };
const obj3 = { id: 1, meta: { tags: ['a', 'c'] } };
isEqual(obj1, obj2); // true
isEqual(obj1, obj3); // falseComparing Arrays
ts
import { isEqual } from '@vielzeug/toolkit';
isEqual([1, [2, 3]], [1, [2, 3]]); // true
isEqual([1, 2], [2, 1]); // false (order matters)Specialized Types
ts
import { isEqual } from '@vielzeug/toolkit';
const d1 = new Date('2024-01-01');
const d2 = new Date('2024-01-01');
isEqual(d1, d2); // true
isEqual(/abc/g, /abc/g); // trueImplementation Notes
- Handles circular references safely.
- Performance-optimized for large objects.
- Functions are compared by reference.
- Throws nothing; safely handles all input types including
nullandundefined.