-
Notifications
You must be signed in to change notification settings - Fork 77
/
index.ts
103 lines (95 loc) · 2.19 KB
/
index.ts
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
export interface DifferenceCreate {
type: "CREATE";
path: (string | number)[];
value: any;
}
export interface DifferenceRemove {
type: "REMOVE";
path: (string | number)[];
oldValue: any;
}
export interface DifferenceChange {
type: "CHANGE";
path: (string | number)[];
value: any;
oldValue: any;
}
export type Difference = DifferenceCreate | DifferenceRemove | DifferenceChange;
interface Options {
cyclesFix: boolean;
}
const richTypes = { Date: true, RegExp: true, String: true, Number: true };
export default function diff(
obj: Record<string, any> | any[],
newObj: Record<string, any> | any[],
options: Partial<Options> = { cyclesFix: true },
_stack: Record<string, any>[] = [],
): Difference[] {
let diffs: Difference[] = [];
const isObjArray = Array.isArray(obj);
for (const key in obj) {
const objKey = obj[key];
const path = isObjArray ? +key : key;
if (!(key in newObj)) {
diffs.push({
type: "REMOVE",
path: [path],
oldValue: obj[key],
});
continue;
}
const newObjKey = newObj[key];
const areCompatibleObjects =
typeof objKey === "object" &&
typeof newObjKey === "object" &&
Array.isArray(objKey) === Array.isArray(newObjKey);
if (
objKey &&
newObjKey &&
areCompatibleObjects &&
!richTypes[Object.getPrototypeOf(objKey)?.constructor?.name] &&
(!options.cyclesFix || !_stack.includes(objKey))
) {
diffs.push.apply(
diffs,
diff(
objKey,
newObjKey,
options,
options.cyclesFix ? _stack.concat([objKey]) : [],
).map((difference) => {
difference.path.unshift(path);
return difference;
}),
);
} else if (
objKey !== newObjKey &&
// treat NaN values as equivalent
!(Number.isNaN(objKey) && Number.isNaN(newObjKey)) &&
!(
areCompatibleObjects &&
(isNaN(objKey)
? objKey + "" === newObjKey + ""
: +objKey === +newObjKey)
)
) {
diffs.push({
path: [path],
type: "CHANGE",
value: newObjKey,
oldValue: objKey,
});
}
}
const isNewObjArray = Array.isArray(newObj);
for (const key in newObj) {
if (!(key in obj)) {
diffs.push({
type: "CREATE",
path: [isNewObjArray ? +key : key],
value: newObj[key],
});
}
}
return diffs;
}