69 lines
2.5 KiB
JavaScript
69 lines
2.5 KiB
JavaScript
const richTypes = ["Date", "RegExp", "String", "Number"];
|
|
const temporalTypes = Object.getOwnPropertyNames(globalThis.Temporal || {});
|
|
export default function diff(obj, newObj, options = { cyclesFix: true }, _stack = []) {
|
|
let diffs = [];
|
|
const isObjArray = Array.isArray(obj);
|
|
for (const key in obj) {
|
|
const value = obj[key];
|
|
const path = isObjArray ? +key : key;
|
|
if (!(key in newObj)) {
|
|
diffs.push({
|
|
type: "REMOVE",
|
|
path: [path],
|
|
oldValue: value,
|
|
});
|
|
continue;
|
|
}
|
|
const newValue = newObj[key];
|
|
const areCompatibleObjects = typeof value === "object" &&
|
|
typeof newValue === "object" &&
|
|
Array.isArray(value) === Array.isArray(newValue);
|
|
// Only compute for non-null objects — primitives and null skip this
|
|
// entirely since Object.getPrototypeOf is expensive to call on every key
|
|
const objConstructor = areCompatibleObjects && value
|
|
? Object.getPrototypeOf(value)?.constructor?.name
|
|
: undefined;
|
|
if (value &&
|
|
newValue &&
|
|
areCompatibleObjects &&
|
|
!richTypes.includes(objConstructor) &&
|
|
!temporalTypes.includes(objConstructor) &&
|
|
(!options.cyclesFix || !_stack.includes(value))) {
|
|
// Recurse into objects and arrays
|
|
if (options.cyclesFix) {
|
|
_stack.push(value);
|
|
}
|
|
const subDiffs = diff(value, newValue, options, _stack);
|
|
if (options.cyclesFix) {
|
|
_stack.pop();
|
|
}
|
|
for (const subDiff of subDiffs) {
|
|
subDiff.path.unshift(path);
|
|
diffs.push(subDiff);
|
|
}
|
|
}
|
|
else if (!(Object.is(value, newValue) ||
|
|
(temporalTypes.includes(objConstructor) &&
|
|
String(value) === String(newValue)) ||
|
|
(richTypes.includes(objConstructor) &&
|
|
(isNaN(value) ? value + "" === newValue + "" : +value === +newValue)))) {
|
|
diffs.push({
|
|
path: [path],
|
|
type: "CHANGE",
|
|
value: newValue,
|
|
oldValue: value,
|
|
});
|
|
}
|
|
}
|
|
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;
|
|
}
|