| 26 | const richTypes = { Date: true, RegExp: true, String: true, Number: true }; |
| 27 | |
| 28 | export default function diff( |
| 29 | obj: Record<string, any> | any[], |
| 30 | newObj: Record<string, any> | any[], |
| 31 | options: Partial<Options> = { cyclesFix: true }, |
| 32 | _stack: Record<string, any>[] = [], |
| 33 | ): Difference[] { |
| 34 | let diffs: Difference[] = []; |
| 35 | const isObjArray = Array.isArray(obj); |
| 36 | |
| 37 | for (const key in obj) { |
| 38 | const objKey = obj[key]; |
| 39 | const path = isObjArray ? +key : key; |
| 40 | if (!(key in newObj)) { |
| 41 | diffs.push({ |
| 42 | type: "REMOVE", |
| 43 | path: [path], |
| 44 | oldValue: obj[key], |
| 45 | }); |
| 46 | continue; |
| 47 | } |
| 48 | const newObjKey = newObj[key]; |
| 49 | const areCompatibleObjects = |
| 50 | typeof objKey === "object" && |
| 51 | typeof newObjKey === "object" && |
| 52 | Array.isArray(objKey) === Array.isArray(newObjKey); |
| 53 | if ( |
| 54 | objKey && |
| 55 | newObjKey && |
| 56 | areCompatibleObjects && |
| 57 | !richTypes[Object.getPrototypeOf(objKey)?.constructor?.name] && |
| 58 | (!options.cyclesFix || !_stack.includes(objKey)) |
| 59 | ) { |
| 60 | diffs.push.apply( |
| 61 | diffs, |
| 62 | diff( |
| 63 | objKey, |
| 64 | newObjKey, |
| 65 | options, |
| 66 | options.cyclesFix ? _stack.concat([objKey]) : [], |
| 67 | ).map((difference) => { |
| 68 | difference.path.unshift(path); |
| 69 | return difference; |
| 70 | }), |
| 71 | ); |
| 72 | } else if ( |
| 73 | objKey !== newObjKey && |
| 74 | // treat NaN values as equivalent |
| 75 | !(Number.isNaN(objKey) && Number.isNaN(newObjKey)) && |
| 76 | !( |
| 77 | areCompatibleObjects && |
| 78 | (isNaN(objKey) |
| 79 | ? objKey + "" === newObjKey + "" |
| 80 | : +objKey === +newObjKey) |
| 81 | ) |
| 82 | ) { |
| 83 | diffs.push({ |
| 84 | path: [path], |
| 85 | type: "CHANGE", |