mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(core): updating deepCopy to avoid max callstack with circular deps (#4468)
* fix(core): updating deepCopy to avoid max callstack in case of circular dep * fix(core): show warning with path added to circular reference
This commit is contained in:
parent
7620d93eda
commit
ca60b0e203
|
@ -1,12 +1,16 @@
|
||||||
/* 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 */
|
/* 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): T => {
|
export const deepCopy = <T>(source: T, hash = new WeakMap(), path = ''): T => {
|
||||||
let clone: any;
|
let clone: any;
|
||||||
let i: any;
|
let i: any;
|
||||||
const hasOwnProp = Object.prototype.hasOwnProperty.bind(source);
|
const hasOwnProp = Object.prototype.hasOwnProperty.bind(source);
|
||||||
// Primitives & Null
|
// Primitives & Null & Function
|
||||||
if (typeof source !== 'object' || source === null) {
|
if (typeof source !== 'object' || source === null || source instanceof Function) {
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
if (hash.has(source)) {
|
||||||
|
console.warn(`Circular reference detected at "source${path}"`);
|
||||||
|
return hash.get(source);
|
||||||
|
}
|
||||||
// Date
|
// Date
|
||||||
if (source instanceof Date) {
|
if (source instanceof Date) {
|
||||||
return new Date(source.getTime()) as T;
|
return new Date(source.getTime()) as T;
|
||||||
|
@ -16,15 +20,16 @@ export const deepCopy = <T>(source: T): T => {
|
||||||
clone = [];
|
clone = [];
|
||||||
const len = source.length;
|
const len = source.length;
|
||||||
for (i = 0; i < len; i++) {
|
for (i = 0; i < len; i++) {
|
||||||
clone[i] = deepCopy(source[i]);
|
clone[i] = deepCopy(source[i], hash, path + `[${i as string}]`);
|
||||||
}
|
}
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
// Object
|
// Object
|
||||||
clone = {};
|
clone = {};
|
||||||
|
hash.set(source, clone);
|
||||||
for (i in source) {
|
for (i in source) {
|
||||||
if (hasOwnProp(i)) {
|
if (hasOwnProp(i)) {
|
||||||
clone[i] = deepCopy((source as any)[i]);
|
clone[i] = deepCopy((source as any)[i], hash, path + `.${i as string}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return clone;
|
return clone;
|
||||||
|
|
|
@ -48,4 +48,40 @@ describe('deepCopy', () => {
|
||||||
expect(copy.deep.props).toEqual(object.deep.props);
|
expect(copy.deep.props).toEqual(object.deep.props);
|
||||||
expect(copy.deep.props).not.toBe(object.deep.props);
|
expect(copy.deep.props).not.toBe(object.deep.props);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should avoid max call stack in case of circular deps', () => {
|
||||||
|
const object: Record<string, any> = {
|
||||||
|
deep: {
|
||||||
|
props: {
|
||||||
|
list: [{ a: 1 }, { b: 2 }, { c: 3 }],
|
||||||
|
},
|
||||||
|
arr: [1, 2, 3],
|
||||||
|
},
|
||||||
|
arr: [
|
||||||
|
{
|
||||||
|
prop: {
|
||||||
|
list: ['a', 'b', 'c'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
func: () => {},
|
||||||
|
date: new Date(),
|
||||||
|
undef: undefined,
|
||||||
|
nil: null,
|
||||||
|
bool: true,
|
||||||
|
num: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
object.circular = object;
|
||||||
|
object.deep.props.circular = 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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue