mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
fix(core): make deepCopy backward compatible (#4505)
* fix(core): make `deepCopy` backward compatible `JSON.parse(JSON.stringify())` uses `.toJSON` when available. so should `deepCopy` * fix(core): prevent double quotes on luxon datetimes (#4508) * 🐛 Prevent double quotes on luxon datetimes * ⚡ Generalize solution * update the types in packages/workflow/src/utils.ts * add `toJSON` check to NodeErrors.isTraversableObject as well * move the toJSON check before the cyclic dependency check * fix(core): keep backward compatibility in deepCopy by calling `toJSON` on objects that have it * fix(core): updating deepCopy typings * Revert "fix(core): updating deepCopy typings" This reverts commit 100a0f1f3d7ddac5425ccc8498381324f418d7a2. * fix(core): temporarily removing Date cloning from deepCopy * fix(core): updating deepCopy types * fix(core): updating deepCopy * fix(core): updating deepCopy get prototype of object Co-authored-by: Iván Ovejero <ivov.src@gmail.com> Co-authored-by: Csaba Tuncsik <csaba@n8n.io>
This commit is contained in:
committed by
GitHub
parent
721ef26d5f
commit
b282c7e5d9
@@ -21,7 +21,7 @@ export function isObject(maybe: unknown): maybe is { [key: string]: unknown } {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isTraversable(maybe: unknown): maybe is IDataObject {
|
function isTraversable(maybe: unknown): maybe is IDataObject {
|
||||||
return isObject(maybe) && Object.keys(maybe).length > 0;
|
return isObject(maybe) && typeof maybe.toJSON !== 'function' && Object.keys(maybe).length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CodeNodeMode = 'runOnceForAllItems' | 'runOnceForEachItem';
|
export type CodeNodeMode = 'runOnceForAllItems' | 'runOnceForEachItem';
|
||||||
|
|||||||
@@ -176,8 +176,12 @@ abstract class NodeError extends ExecutionBaseError {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
protected isTraversableObject(value: any): value is JsonObject {
|
protected isTraversableObject(value: any): value is JsonObject {
|
||||||
return (
|
return (
|
||||||
|
value &&
|
||||||
|
typeof value === 'object' &&
|
||||||
|
!Array.isArray(value) &&
|
||||||
|
typeof value.toJSON !== 'function' &&
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||||
value && typeof value === 'object' && !Array.isArray(value) && !!Object.keys(value).length
|
!!Object.keys(value).length
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,38 @@
|
|||||||
/* 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, hash = new WeakMap(), path = ''): T => {
|
type Primitives = string | number | boolean | bigint | symbol | null | undefined;
|
||||||
let clone: any;
|
export const deepCopy = <T extends ((object | Date) & { toJSON?: () => string }) | Primitives>(
|
||||||
let i: any;
|
source: T,
|
||||||
|
hash = new WeakMap(),
|
||||||
|
path = '',
|
||||||
|
): T => {
|
||||||
const hasOwnProp = Object.prototype.hasOwnProperty.bind(source);
|
const hasOwnProp = Object.prototype.hasOwnProperty.bind(source);
|
||||||
// Primitives & Null & Function
|
// Primitives & Null & Function
|
||||||
if (typeof source !== 'object' || source === null || source instanceof Function) {
|
if (typeof source !== 'object' || source === null || typeof source === 'function') {
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
// Date and other objects with toJSON method
|
||||||
|
// TODO: remove this when other code parts not expecting objects with `.toJSON` method called and add back checking for Date and cloning it properly
|
||||||
|
if (typeof source.toJSON === 'function') {
|
||||||
|
return source.toJSON() as T;
|
||||||
|
}
|
||||||
if (hash.has(source)) {
|
if (hash.has(source)) {
|
||||||
return hash.get(source);
|
return hash.get(source);
|
||||||
}
|
}
|
||||||
// Date
|
|
||||||
if (source instanceof Date) {
|
|
||||||
return new Date(source.getTime()) as T;
|
|
||||||
}
|
|
||||||
// Array
|
// Array
|
||||||
if (Array.isArray(source)) {
|
if (Array.isArray(source)) {
|
||||||
clone = [];
|
const clone = [];
|
||||||
const len = source.length;
|
const len = source.length;
|
||||||
for (i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
clone[i] = deepCopy(source[i], hash, path + `[${i as string}]`);
|
clone[i] = deepCopy(source[i], hash, path + `[${i}]`);
|
||||||
}
|
}
|
||||||
return clone;
|
return clone as T;
|
||||||
}
|
}
|
||||||
// Object
|
// Object
|
||||||
clone = {};
|
const clone = Object.create(Object.getPrototypeOf({}));
|
||||||
hash.set(source, clone);
|
hash.set(source, clone);
|
||||||
for (i in source) {
|
for (const i in source) {
|
||||||
if (hasOwnProp(i)) {
|
if (hasOwnProp(i)) {
|
||||||
clone[i] = deepCopy((source as any)[i], hash, path + `.${i as string}`);
|
clone[i] = deepCopy((source as any)[i], hash, path + `.${i}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return clone;
|
return clone;
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ describe('jsonParse', () => {
|
|||||||
|
|
||||||
describe('deepCopy', () => {
|
describe('deepCopy', () => {
|
||||||
it('should deep copy an object', () => {
|
it('should deep copy an object', () => {
|
||||||
|
const serializable = {
|
||||||
|
x: 1,
|
||||||
|
y: 2,
|
||||||
|
toJSON: () => 'x:1,y:2',
|
||||||
|
};
|
||||||
const object = {
|
const object = {
|
||||||
deep: {
|
deep: {
|
||||||
props: {
|
props: {
|
||||||
@@ -26,6 +31,7 @@ describe('deepCopy', () => {
|
|||||||
},
|
},
|
||||||
arr: [1, 2, 3],
|
arr: [1, 2, 3],
|
||||||
},
|
},
|
||||||
|
serializable,
|
||||||
arr: [
|
arr: [
|
||||||
{
|
{
|
||||||
prop: {
|
prop: {
|
||||||
@@ -34,17 +40,18 @@ describe('deepCopy', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
func: () => {},
|
func: () => {},
|
||||||
date: new Date(),
|
date: new Date(1667389172201),
|
||||||
undef: undefined,
|
undef: undefined,
|
||||||
nil: null,
|
nil: null,
|
||||||
bool: true,
|
bool: true,
|
||||||
num: 1,
|
num: 1,
|
||||||
};
|
};
|
||||||
const copy = deepCopy(object);
|
const copy = deepCopy(object);
|
||||||
expect(copy).toEqual(object);
|
|
||||||
expect(copy).not.toBe(object);
|
expect(copy).not.toBe(object);
|
||||||
expect(copy.arr).toEqual(object.arr);
|
expect(copy.arr).toEqual(object.arr);
|
||||||
expect(copy.arr).not.toBe(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).toEqual(object.deep.props);
|
||||||
expect(copy.deep.props).not.toBe(object.deep.props);
|
expect(copy.deep.props).not.toBe(object.deep.props);
|
||||||
});
|
});
|
||||||
@@ -65,7 +72,7 @@ describe('deepCopy', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
func: () => {},
|
func: () => {},
|
||||||
date: new Date(),
|
date: new Date(1667389172201),
|
||||||
undef: undefined,
|
undef: undefined,
|
||||||
nil: null,
|
nil: null,
|
||||||
bool: true,
|
bool: true,
|
||||||
@@ -74,14 +81,16 @@ describe('deepCopy', () => {
|
|||||||
|
|
||||||
object.circular = object;
|
object.circular = object;
|
||||||
object.deep.props.circular = object;
|
object.deep.props.circular = object;
|
||||||
object.deep.arr.push(object)
|
object.deep.arr.push(object);
|
||||||
|
|
||||||
const copy = deepCopy(object);
|
const copy = deepCopy(object);
|
||||||
expect(copy).toEqual(object);
|
|
||||||
expect(copy).not.toBe(object);
|
expect(copy).not.toBe(object);
|
||||||
expect(copy.arr).toEqual(object.arr);
|
expect(copy.arr).toEqual(object.arr);
|
||||||
expect(copy.arr).not.toBe(object.arr);
|
expect(copy.arr).not.toBe(object.arr);
|
||||||
expect(copy.deep.props).toEqual(object.deep.props);
|
expect(copy.date).toBe('2022-11-02T11:39:32.201Z');
|
||||||
expect(copy.deep.props).not.toBe(object.deep.props);
|
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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user