Attempting to use if mirror has a toJSON and object does not breaks
For example, suppose you have the following objects:
const object = {
bug: new Date('2032-01-04'),
nobug: new Date('2032-01-04')
};
const mirror = {
bug: new Date('2032-01-04'),
nobug: new Date('2032-01-04').toJSON()
};
Then compare(mirror, obj); returns the following array:
[{
op: "replace",
path: "/nobug",
value: "2032-01-04T00:00:00.000Z"
}, {
op: "add",
path: "/bug/0",
value: "2"
}, {
op: "add",
path: "/bug/1",
value: "0"
}, {
op: "add",
path: "/bug/2",
value: "3"
}, {
op: "add",
path: "/bug/3",
value: "2"
}, {
op: "add",
path: "/bug/4",
value: "-"
}, {
op: "add",
path: "/bug/5",
value: "0"
}, {
op: "add",
path: "/bug/6",
value: "1"
}, {
op: "add",
path: "/bug/7",
value: "-"
}, {
op: "add",
path: "/bug/8",
value: "0"
}, {
op: "add",
path: "/bug/9",
value: "4"
}, {
op: "add",
path: "/bug/10",
value: "T"
}, {
op: "add",
path: "/bug/11",
value: "0"
}, {
op: "add",
path: "/bug/12",
value: "0"
}, {
op: "add",
path: "/bug/13",
value: ":"
}, {
op: "add",
path: "/bug/14",
value: "0"
}, {
op: "add",
path: "/bug/15",
value: "0"
}, {
op: "add",
path: "/bug/16",
value: ":"
}, {
op: "add",
path: "/bug/17",
value: "0"
}, {
op: "add",
path: "/bug/18",
value: "0"
}, {
op: "add",
path: "/bug/19",
value: "."
}, {
op: "add",
path: "/bug/20",
value: "0"
}, {
op: "add",
path: "/bug/21",
value: "0"
}, {
op: "add",
path: "/bug/22",
value: "0"
}, {
op: "add",
path: "/bug/23",
value: "Z"
}]
This issue persists since 2021 which is really bad for such a far reaching library. We have the same issue. It happens when both objects contain a Date. Duplicate of #278
Workaround: when creating the objects to compare, use toIsoString beforehand. e.g. bug = new Date('2032-01-04').toIsoString();
Error Source: // src/duplex.ts line 148 to 149 if (typeof obj.toJSON === "function") { obj = obj.toJSON(); } Explanation:
- When checking for equality in the _generate function, having a Date object in both mirror and obj will cause line 166 to return true. (since typeof myDate is also object)
- this means the _generate function will be called with "object.bug" and "mirror.bug"
- line 148 will convert object to its JSON version -> a string representing new Date('2032-01-04') which is "2032-01-04T00:00:00"
- however, the mirror object will not be converted to json.
- this means newKeys will have all the characters of the string in it
- but oldKeys will be an empty array
- this causes line 198 to 203 to be reached, resulting in all the "add" operations
Possible Solution:
- check typeof obj.toJSON === "function" and replacing obj and mirror with the result before the type check in line 166. This would prevent the call to _generate() with "object.bug" and "mirror.bug" and will terminate without recursion.
Here's a little helper function which patches Date values - it traverses over arrays and objects:
/**
* The function takes an input value and applies data type patches if the input value is:
* - `Date` --> JSON representation as fast-json-patch cannot handle Dates
* - an `object` --> traverses the object tree applying patches to properties where necessary
* - an `array` --> traverses the array elements applying patches to properties where necessary
*
* All other data types - e.g. `string`, `number` will stay unpatched.
*
* @returns patched input when necessary
*/
export function patchDataTypes<T>(input: T): any {
if (input instanceof Date) {
return input.toJSON() as string;
} else if (typeof input === 'object' && input !== null) {
if (Array.isArray(input)) {
// If it's an array, recursively convert each element
return input.map(patchDataTypes);
} else {
// If it's an object, recursively convert each property
const result: Record<string, unknown> = {};
for (const key in input) {
result[key] = patchDataTypes(input[key]);
}
return result;
}
} else {
return input;
}
}