mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
perf: update deepCopy (#4364)
* perf: update deepCopy * fix: using deepCopy in core and cli packages * fix: using deepCopy in editor * chore: formatting * fix: some micro optimisation in deepCopy
This commit is contained in:
parent
638d6f60d3
commit
1aa21ed3df
|
@ -1,6 +1,6 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
/* eslint-disable no-underscore-dangle */
|
/* eslint-disable no-underscore-dangle */
|
||||||
import { ICredentialDataDecryptedObject } from 'n8n-workflow';
|
import { deepCopy, ICredentialDataDecryptedObject } from 'n8n-workflow';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
import { CredentialTypes, GenericHelpers, ICredentialsOverwrite } from '.';
|
import { CredentialTypes, GenericHelpers, ICredentialsOverwrite } from '.';
|
||||||
|
@ -19,7 +19,7 @@ class CredentialsOverwritesClass {
|
||||||
if (overwriteData !== undefined) {
|
if (overwriteData !== undefined) {
|
||||||
// If data is already given it can directly be set instead of
|
// If data is already given it can directly be set instead of
|
||||||
// loaded from environment
|
// loaded from environment
|
||||||
this.__setData(JSON.parse(JSON.stringify(overwriteData)));
|
this.__setData(deepCopy(overwriteData));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,14 +57,11 @@ class CredentialsOverwritesClass {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
const returnData = deepCopy(data);
|
||||||
const returnData = JSON.parse(JSON.stringify(data));
|
|
||||||
// Overwrite only if there is currently no data set
|
// Overwrite only if there is currently no data set
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (const key of Object.keys(overwrites)) {
|
for (const key of Object.keys(overwrites)) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
// @ts-ignore
|
||||||
if ([null, undefined, ''].includes(returnData[key])) {
|
if ([null, undefined, ''].includes(returnData[key])) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
||||||
returnData[key] = overwrites[key];
|
returnData[key] = overwrites[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,7 @@ import {
|
||||||
NodeParameterValueType,
|
NodeParameterValueType,
|
||||||
NodeExecutionWithMetadata,
|
NodeExecutionWithMetadata,
|
||||||
IPairedItemData,
|
IPairedItemData,
|
||||||
|
deepCopy,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import { Agent } from 'https';
|
import { Agent } from 'https';
|
||||||
|
@ -1641,7 +1642,7 @@ export async function getCredentials(
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export function getNode(node: INode): INode {
|
export function getNode(node: INode): INode {
|
||||||
return JSON.parse(JSON.stringify(node));
|
return deepCopy(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
USER_SETTINGS_FILE_NAME,
|
USER_SETTINGS_FILE_NAME,
|
||||||
USER_SETTINGS_SUBFOLDER,
|
USER_SETTINGS_SUBFOLDER,
|
||||||
} from '.';
|
} from '.';
|
||||||
|
import { deepCopy } from 'n8n-workflow';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const { promisify } = require('util');
|
const { promisify } = require('util');
|
||||||
|
@ -173,7 +174,7 @@ export async function writeUserSettings(
|
||||||
}
|
}
|
||||||
|
|
||||||
await fsWriteFile(settingsPath, JSON.stringify(settingsToWrite, null, '\t'));
|
await fsWriteFile(settingsPath, JSON.stringify(settingsToWrite, null, '\t'));
|
||||||
settingsCache = JSON.parse(JSON.stringify(userSettings));
|
settingsCache = deepCopy(userSettings);
|
||||||
|
|
||||||
return userSettings;
|
return userSettings;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ import {
|
||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
deepCopy,
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
INodePropertyOptions,
|
INodePropertyOptions,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
@ -161,7 +162,7 @@ export default mixins(
|
||||||
} else {
|
} else {
|
||||||
// Everything else saves them directly as an array.
|
// Everything else saves them directly as an array.
|
||||||
newValue = get(this.nodeValues, `${this.path}.${optionName}`, []);
|
newValue = get(this.nodeValues, `${this.path}.${optionName}`, []);
|
||||||
newValue.push(JSON.parse(JSON.stringify(option.default)));
|
newValue.push(deepCopy(option.default));
|
||||||
}
|
}
|
||||||
|
|
||||||
parameterData = {
|
parameterData = {
|
||||||
|
@ -172,7 +173,7 @@ export default mixins(
|
||||||
// Add a new option
|
// Add a new option
|
||||||
parameterData = {
|
parameterData = {
|
||||||
name,
|
name,
|
||||||
value: JSON.parse(JSON.stringify(option.default)),
|
value: deepCopy(option.default),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,8 +113,10 @@ import {
|
||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
deepCopy,
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
INodePropertyCollection,
|
INodePropertyCollection,
|
||||||
|
NodeParameterValue,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
|
@ -249,13 +251,13 @@ export default mixins(genericHelpers)
|
||||||
// Multiple values are allowed so append option to array
|
// Multiple values are allowed so append option to array
|
||||||
newParameterValue[optionParameter.name] = get(this.nodeValues, `${this.path}.${optionParameter.name}`, []);
|
newParameterValue[optionParameter.name] = get(this.nodeValues, `${this.path}.${optionParameter.name}`, []);
|
||||||
if (Array.isArray(optionParameter.default)) {
|
if (Array.isArray(optionParameter.default)) {
|
||||||
(newParameterValue[optionParameter.name] as INodeParameters[]).push(...JSON.parse(JSON.stringify(optionParameter.default)));
|
(newParameterValue[optionParameter.name] as INodeParameters[]).push(...deepCopy(optionParameter.default as INodeParameters[]));
|
||||||
} else if (optionParameter.default !== '' && typeof optionParameter.default !== 'object') {
|
} else if (optionParameter.default !== '' && typeof optionParameter.default !== 'object') {
|
||||||
(newParameterValue[optionParameter.name] as INodeParameters[]).push(JSON.parse(JSON.stringify(optionParameter.default)));
|
(newParameterValue[optionParameter.name] as NodeParameterValue[]).push(deepCopy(optionParameter.default));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Add a new option
|
// Add a new option
|
||||||
newParameterValue[optionParameter.name] = JSON.parse(JSON.stringify(optionParameter.default));
|
newParameterValue[optionParameter.name] = deepCopy(optionParameter.default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ import { get } from 'lodash';
|
||||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||||
|
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
|
import { deepCopy } from "n8n-workflow";
|
||||||
|
|
||||||
export default mixins(genericHelpers)
|
export default mixins(genericHelpers)
|
||||||
.extend({
|
.extend({
|
||||||
|
@ -87,7 +88,7 @@ export default mixins(genericHelpers)
|
||||||
currentValue = [];
|
currentValue = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
currentValue.push(JSON.parse(JSON.stringify(this.parameter.default)));
|
currentValue.push(deepCopy(this.parameter.default));
|
||||||
|
|
||||||
const parameterData = {
|
const parameterData = {
|
||||||
name,
|
name,
|
||||||
|
|
|
@ -122,6 +122,7 @@ import {
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
NodeHelpers,
|
NodeHelpers,
|
||||||
NodeParameterValue,
|
NodeParameterValue,
|
||||||
|
deepCopy,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { INodeUi, INodeUpdatePropertiesInformation, IUpdateInformation } from '@/Interface';
|
import { INodeUi, INodeUpdatePropertiesInformation, IUpdateInformation } from '@/Interface';
|
||||||
|
|
||||||
|
@ -427,7 +428,7 @@ export default mixins(externalHooks, genericHelpers, nodeHelpers).extend({
|
||||||
// Value should be set
|
// Value should be set
|
||||||
if (typeof value === 'object') {
|
if (typeof value === 'object') {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
Vue.set(get(this.nodeValues, nameParts.join('.')), lastNamePart, JSON.parse(JSON.stringify(value)));
|
Vue.set(get(this.nodeValues, nameParts.join('.')), lastNamePart, deepCopy(value));
|
||||||
} else {
|
} else {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
Vue.set(get(this.nodeValues, nameParts.join('.')), lastNamePart, value);
|
Vue.set(get(this.nodeValues, nameParts.join('.')), lastNamePart, value);
|
||||||
|
@ -500,7 +501,7 @@ export default mixins(externalHooks, genericHelpers, nodeHelpers).extend({
|
||||||
|
|
||||||
// Copy the data because it is the data of vuex so make sure that
|
// Copy the data because it is the data of vuex so make sure that
|
||||||
// we do not edit it directly
|
// we do not edit it directly
|
||||||
nodeParameters = JSON.parse(JSON.stringify(nodeParameters));
|
nodeParameters = deepCopy(nodeParameters);
|
||||||
|
|
||||||
for (const parameterName of Object.keys(parameterData.value)) {
|
for (const parameterName of Object.keys(parameterData.value)) {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
|
@ -591,7 +592,7 @@ export default mixins(externalHooks, genericHelpers, nodeHelpers).extend({
|
||||||
|
|
||||||
// Copy the data because it is the data of vuex so make sure that
|
// Copy the data because it is the data of vuex so make sure that
|
||||||
// we do not edit it directly
|
// we do not edit it directly
|
||||||
nodeParameters = JSON.parse(JSON.stringify(nodeParameters));
|
nodeParameters = deepCopy(nodeParameters);
|
||||||
|
|
||||||
// Remove the 'parameters.' from the beginning to just have the
|
// Remove the 'parameters.' from the beginning to just have the
|
||||||
// actual parameter name
|
// actual parameter name
|
||||||
|
@ -734,7 +735,7 @@ export default mixins(externalHooks, genericHelpers, nodeHelpers).extend({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Vue.set(this.nodeValues, 'parameters', JSON.parse(JSON.stringify(this.node.parameters)));
|
Vue.set(this.nodeValues, 'parameters', deepCopy(this.node.parameters));
|
||||||
} else {
|
} else {
|
||||||
this.nodeValid = false;
|
this.nodeValid = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,6 +97,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
deepCopy,
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
|
@ -288,7 +289,7 @@ export default mixins(
|
||||||
|
|
||||||
if (parameterGotResolved === true) {
|
if (parameterGotResolved === true) {
|
||||||
if (this.path) {
|
if (this.path) {
|
||||||
rawValues = JSON.parse(JSON.stringify(this.nodeValues));
|
rawValues = deepCopy(this.nodeValues);
|
||||||
set(rawValues, this.path, nodeValues);
|
set(rawValues, this.path, nodeValues);
|
||||||
return this.displayParameter(rawValues, parameter, this.path, this.node);
|
return this.displayParameter(rawValues, parameter, this.path, this.node);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -192,6 +192,7 @@ import { WORKFLOW_SETTINGS_MODAL_KEY } from '../constants';
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
|
|
||||||
import { mapGetters } from "vuex";
|
import { mapGetters } from "vuex";
|
||||||
|
import { deepCopy } from "n8n-workflow";
|
||||||
|
|
||||||
export default mixins(
|
export default mixins(
|
||||||
externalHooks,
|
externalHooks,
|
||||||
|
@ -274,7 +275,7 @@ export default mixins(
|
||||||
this.$showError(error, 'Problem loading settings', 'The following error occurred loading the data:');
|
this.$showError(error, 'Problem loading settings', 'The following error occurred loading the data:');
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflowSettings = JSON.parse(JSON.stringify(this.$store.getters.workflowSettings));
|
const workflowSettings = deepCopy(this.$store.getters.workflowSettings);
|
||||||
|
|
||||||
if (workflowSettings.timezone === undefined) {
|
if (workflowSettings.timezone === undefined) {
|
||||||
workflowSettings.timezone = 'DEFAULT';
|
workflowSettings.timezone = 'DEFAULT';
|
||||||
|
@ -536,7 +537,7 @@ export default mixins(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldSettings = JSON.parse(JSON.stringify(this.$store.getters.workflowSettings));
|
const oldSettings = deepCopy(this.$store.getters.workflowSettings);
|
||||||
|
|
||||||
this.$store.commit('setWorkflowSettings', localWorkflowSettings);
|
this.$store.commit('setWorkflowSettings', localWorkflowSettings);
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ import {
|
||||||
IExecuteData,
|
IExecuteData,
|
||||||
INodeConnection,
|
INodeConnection,
|
||||||
IWebhookDescription,
|
IWebhookDescription,
|
||||||
|
deepCopy,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -361,8 +362,8 @@ export const workflowHelpers = mixins(
|
||||||
cachedWorkflow = new Workflow({
|
cachedWorkflow = new Workflow({
|
||||||
id: workflowId,
|
id: workflowId,
|
||||||
name: workflowName,
|
name: workflowName,
|
||||||
nodes: copyData ? JSON.parse(JSON.stringify(nodes)) : nodes,
|
nodes: copyData ? deepCopy(nodes) : nodes,
|
||||||
connections: copyData? JSON.parse(JSON.stringify(connections)): connections,
|
connections: copyData? deepCopy(connections): connections,
|
||||||
active: false,
|
active: false,
|
||||||
nodeTypes,
|
nodeTypes,
|
||||||
settings: this.$store.getters.workflowSettings,
|
settings: this.$store.getters.workflowSettings,
|
||||||
|
|
|
@ -99,6 +99,7 @@ import * as CanvasHelpers from './canvasHelpers';
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import {
|
import {
|
||||||
|
deepCopy,
|
||||||
IConnection,
|
IConnection,
|
||||||
IConnections,
|
IConnections,
|
||||||
IDataObject,
|
IDataObject,
|
||||||
|
@ -459,7 +460,7 @@ export default mixins(
|
||||||
this.$store.commit('setWorkflowExecutionData', data);
|
this.$store.commit('setWorkflowExecutionData', data);
|
||||||
this.$store.commit('setWorkflowPinData', data.workflowData.pinData);
|
this.$store.commit('setWorkflowPinData', data.workflowData.pinData);
|
||||||
|
|
||||||
await this.addNodes(JSON.parse(JSON.stringify(data.workflowData.nodes)), JSON.parse(JSON.stringify(data.workflowData.connections)));
|
await this.addNodes(deepCopy(data.workflowData.nodes), deepCopy(data.workflowData.connections));
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.zoomToFit();
|
this.zoomToFit();
|
||||||
this.$store.commit('setStateDirty', false);
|
this.$store.commit('setStateDirty', false);
|
||||||
|
@ -2193,7 +2194,7 @@ export default mixins(
|
||||||
|
|
||||||
// Deep copy the data so that data on lower levels of the node-properties do
|
// Deep copy the data so that data on lower levels of the node-properties do
|
||||||
// not share objects
|
// not share objects
|
||||||
const newNodeData = JSON.parse(JSON.stringify(this.getNodeDataToSave(node)));
|
const newNodeData = deepCopy(this.getNodeDataToSave(node));
|
||||||
newNodeData.id = uuid();
|
newNodeData.id = uuid();
|
||||||
|
|
||||||
// Check if node-name is unique else find one that is
|
// Check if node-name is unique else find one that is
|
||||||
|
|
|
@ -16,3 +16,4 @@ export * from './WorkflowDataProxy';
|
||||||
export * from './WorkflowErrors';
|
export * from './WorkflowErrors';
|
||||||
export * from './WorkflowHooks';
|
export * from './WorkflowHooks';
|
||||||
export { LoggerProxy, NodeHelpers, ObservableObject, TelemetryHelpers };
|
export { LoggerProxy, NodeHelpers, ObservableObject, TelemetryHelpers };
|
||||||
|
export { deepCopy } from './utils';
|
||||||
|
|
34
packages/workflow/src/utils.test.ts
Normal file
34
packages/workflow/src/utils.test.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { deepCopy } from './utils';
|
||||||
|
|
||||||
|
describe('deepCopy', () => {
|
||||||
|
it('should deep copy an object', () => {
|
||||||
|
const object = {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1 +1,32 @@
|
||||||
export const deepCopy = <T>(toCopy: T) => JSON.parse(JSON.stringify(toCopy)) as T;
|
/* 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 => {
|
||||||
|
let clone: any;
|
||||||
|
let i: any;
|
||||||
|
const hasOwnProp = Object.prototype.hasOwnProperty.bind(source);
|
||||||
|
// Primitives & Null
|
||||||
|
if (typeof source !== 'object' || source === null) {
|
||||||
|
return 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]);
|
||||||
|
}
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
// Object
|
||||||
|
clone = {};
|
||||||
|
for (i in source) {
|
||||||
|
if (hasOwnProp(i)) {
|
||||||
|
clone[i] = deepCopy((source as any)[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clone;
|
||||||
|
};
|
||||||
|
// eslint-enable
|
||||||
|
|
Loading…
Reference in a new issue