Deep Equality For Maps Sets And Cycles
Compare nested values by structure, including dates, regexes, unordered maps and sets, with cycle detection and no JSON serialization.
Code
function isObject(value: unknown): value is Record<PropertyKey, unknown> {
return value !== null && typeof value === "object";
}
export function deepEqual(left: unknown, right: unknown): boolean {
return compare(left, right, new WeakMap<object, WeakSet<object>>());
}
function compare(
left: unknown,
right: unknown,
seen: WeakMap<object, WeakSet<object>>,
): boolean {
if (Object.is(left, right)) return true;
if (!isObject(left) || !isObject(right)) return false;
if (Object.getPrototypeOf(left) !== Object.getPrototypeOf(right)) return false;
let partners = seen.get(left);
if (partners?.has(right)) return true;
if (!partners) {
partners = new WeakSet<object>();
seen.set(left, partners);
}
partners.add(right);
if (left instanceof Date && right instanceof Date) {
return Object.is(left.getTime(), right.getTime());
}
if (left instanceof RegExp && right instanceof RegExp) {
return left.source === right.source && left.flags === right.flags;
}
if (left instanceof Map && right instanceof Map) {
if (left.size !== right.size) return false;
for (const [key, value] of left) {
let found = false;
for (const [otherKey, otherValue] of right) {
if (compare(key, otherKey, seen) && compare(value, otherValue, seen)) {
found = true;
break;
}
}
if (!found) return false;
}
return true;
}
if (left instanceof Set && right instanceof Set) {
if (left.size !== right.size) return false;
for (const value of left) {
let found = false;
for (const other of right) {
if (compare(value, other, seen)) {
found = true;
break;
}
}
if (!found) return false;
}
return true;
}
if (Array.isArray(left) || Array.isArray(right)) {
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
return false;
}
for (let index = 0; index < left.length; index += 1) {
if (!compare(left[index], right[index], seen)) return false;
}
return true;
}
const leftKeys = Reflect.ownKeys(left);
const rightKeys = Reflect.ownKeys(right);
if (leftKeys.length !== rightKeys.length) return false;
for (const key of leftKeys) {
if (!Object.prototype.hasOwnProperty.call(right, key)) return false;
if (!compare(left[key], right[key], seen)) return false;
}
return true;
}Report a problem with this exampleOpens a pre-filled issue on GitHub.