fix(core): make deepCopy backward compatible

`JSON.parse(JSON.stringify())`  uses `.toJSON` when available. so should `deepCopy`
This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2022-11-02 12:52:22 +01:00
parent 3c9ad02ce3
commit c459d58579
2 changed files with 35 additions and 17 deletions

View file

@ -1,7 +1,13 @@
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument */
export const deepCopy = <T>(source: T, hash = new WeakMap(), path = ''): T => {
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return */
type Serializable = any & { toJSON?: (key?: any) => string };
export const deepCopy = <T extends any | Serializable>(
source: T,
hash = new WeakMap(),
path = '',
): T => {
let clone: any;
let i: any;
const hasOwnProp = Object.prototype.hasOwnProperty.bind(source);
// Primitives & Null & Function
if (typeof source !== 'object' || source === null || source instanceof Function) {
@ -10,23 +16,26 @@ export const deepCopy = <T>(source: T, hash = new WeakMap(), path = ''): T => {
if (hash.has(source)) {
return hash.get(source);
}
// Date
if (source instanceof Date) {
return new Date(source.getTime()) as T;
}
// Array
if (Array.isArray(source)) {
clone = [];
const len = source.length;
for (i = 0; i < len; i++) {
clone[i] = deepCopy(source[i], hash, path + `[${i as string}]`);
for (let i = 0; i < len; i++) {
clone[i] = deepCopy(source[i], hash, path + `[${i}]`);
}
return clone;
}
// Date and other Serializable objects
const toJSON = (source as Serializable).toJSON;
if (typeof toJSON === 'function') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
return toJSON.call(source) as T;
}
// Object
clone = {};
hash.set(source, clone);
for (i in source) {
for (const i in source) {
if (hasOwnProp(i)) {
clone[i] = deepCopy((source as any)[i], hash, path + `.${i as string}`);
}

View file

@ -19,6 +19,11 @@ describe('jsonParse', () => {
describe('deepCopy', () => {
it('should deep copy an object', () => {
const serializable = {
x: 1,
y: 2,
toJSON: () => 'x:1,y:2',
};
const object = {
deep: {
props: {
@ -26,6 +31,7 @@ describe('deepCopy', () => {
},
arr: [1, 2, 3],
},
serializable,
arr: [
{
prop: {
@ -34,17 +40,18 @@ describe('deepCopy', () => {
},
],
func: () => {},
date: new Date(),
date: new Date(1667389172201),
undef: undefined,
nil: null,
bool: true,
num: 1,
};
const copy = deepCopy(object);
expect(copy).toEqual(object);
expect(copy).not.toBe(object);
expect(copy.arr).toEqual(object.arr);
expect(copy.arr).not.toBe(object.arr);
expect(copy.date).toBe('2022-11-02T11:39:32.201Z');
expect(copy.serializable).toBe(serializable.toJSON());
expect(copy.deep.props).toEqual(object.deep.props);
expect(copy.deep.props).not.toBe(object.deep.props);
});
@ -65,7 +72,7 @@ describe('deepCopy', () => {
},
],
func: () => {},
date: new Date(),
date: new Date(1667389172201),
undef: undefined,
nil: null,
bool: true,
@ -74,14 +81,16 @@ describe('deepCopy', () => {
object.circular = object;
object.deep.props.circular = object;
object.deep.arr.push(object)
object.deep.arr.push(object);
const copy = deepCopy(object);
expect(copy).toEqual(object);
expect(copy).not.toBe(object);
expect(copy.arr).toEqual(object.arr);
expect(copy.arr).not.toBe(object.arr);
expect(copy.deep.props).toEqual(object.deep.props);
expect(copy.deep.props).not.toBe(object.deep.props);
expect(copy.date).toBe('2022-11-02T11:39:32.201Z');
expect(copy.deep.props.circular).toBe(copy);
expect(copy.deep.props.circular).not.toBe(object);
expect(copy.deep.arr.slice(-1)[0]).toBe(copy);
expect(copy.deep.arr.slice(-1)[0]).not.toBe(object);
});
});