-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdeepEquals.ts
More file actions
118 lines (98 loc) · 3.15 KB
/
Copy pathdeepEquals.ts
File metadata and controls
118 lines (98 loc) · 3.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/*
* Copyright (c) 2025 Michael Federczuk
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { getOwnPropertyDescriptor, getPropertyKeys, isNotPrimitive, NonPrimitive } from "./_internal/utils";
import { deepFreeze } from "./deepFreeze";
export interface DeepEqualsOptions {
/**
* Ignores the order that the properties are defined.
*
* Default is `true`.
*/
ignoreOrder?: boolean;
}
const contentsEqual = (array1: unknown[], array2: unknown[], ignoreOrder: boolean): boolean => {
const length: number = array1.length;
if (length !== array2.length) {
return false;
}
if (ignoreOrder) {
for (let i = 0; i < length; ++i) {
if (!(array2.includes(array1[i]))) {
return false;
}
}
} else {
for (let i = 0; i < length; ++i) {
if (array1[i] !== array2[i]) {
return false;
}
}
}
return true;
};
const deepEqualPropertyDescriptor = (descriptor1: PropertyDescriptor, descriptor2: PropertyDescriptor): boolean => {
if ((descriptor1.configurable !== descriptor2.configurable) ||
(descriptor1.enumerable !== descriptor2.enumerable) ||
(descriptor1.writable !== descriptor2.writable) ||
("value" in descriptor1) !== ("value" in descriptor2)) {
return false;
}
if (("value" in descriptor1) && ("value" in descriptor2)) {
if (!(deepEquals(descriptor1.value, descriptor2.value))) {
return false;
}
}
if ((descriptor1.get !== descriptor2.get) || (descriptor1.set !== descriptor2.set)) {
return false;
}
return true;
};
const deepEqualProperty = <T extends NonPrimitive>(obj1: T, obj2: T, propertyKey: keyof T): boolean => {
const obj1PropDescriptor: PropertyDescriptor = getOwnPropertyDescriptor(obj1, propertyKey);
const obj2PropDescriptor: PropertyDescriptor = getOwnPropertyDescriptor(obj2, propertyKey);
return deepEqualPropertyDescriptor(obj1PropDescriptor, obj2PropDescriptor);
};
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
const deepEqualsInternal = <T1 extends NonPrimitive, T2 extends NonPrimitive>(
obj1: T1,
obj2: T2,
options: Readonly<DeepEqualsOptions> | undefined,
): boolean => {
const obj1Keys: (keyof T1)[] = getPropertyKeys(obj1);
const obj2Keys: (keyof T2)[] = getPropertyKeys(obj2);
if (!(contentsEqual(obj1Keys, obj2Keys, options?.ignoreOrder ?? true))) {
return false;
}
if (Object.getPrototypeOf(obj1) !== Object.getPrototypeOf(obj2)) {
return false;
}
return obj1Keys
.every((key: keyof T1): boolean => {
return deepEqualProperty(obj1, obj2 as unknown as T1, key);
});
};
/**
* Checks if **obj1** and **obj2** are equal by recursing through their properties.
*
* @param obj1 The first object to compare.
* @param obj2 The second object to compare.
* @param options Options object to change the behavior of `deepEquals`.
*
* @returns `true` if **obj1** and **obj2** are deeply equal, `false` if otherwise.
*/
export function deepEquals(
obj1: unknown,
obj2: unknown,
options?: Readonly<DeepEqualsOptions>,
): boolean {
if (!(isNotPrimitive(obj1)) || !(isNotPrimitive(obj2))) {
if (Number.isNaN(obj1) && Number.isNaN(obj2)) {
return true;
}
return (obj1 === obj2);
}
return deepEqualsInternal(obj1, obj2, options);
}
deepFreeze(deepEquals);