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:
Csaba Tuncsik 2022-10-18 13:33:31 +02:00 committed by GitHub
parent 638d6f60d3
commit 1aa21ed3df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 101 additions and 27 deletions

View file

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable no-underscore-dangle */
import { ICredentialDataDecryptedObject } from 'n8n-workflow';
import { deepCopy, ICredentialDataDecryptedObject } from 'n8n-workflow';
// eslint-disable-next-line import/no-cycle
import { CredentialTypes, GenericHelpers, ICredentialsOverwrite } from '.';
@ -19,7 +19,7 @@ class CredentialsOverwritesClass {
if (overwriteData !== undefined) {
// If data is already given it can directly be set instead of
// loaded from environment
this.__setData(JSON.parse(JSON.stringify(overwriteData)));
this.__setData(deepCopy(overwriteData));
return;
}
@ -57,14 +57,11 @@ class CredentialsOverwritesClass {
return data;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const returnData = JSON.parse(JSON.stringify(data));
const returnData = deepCopy(data);
// Overwrite only if there is currently no data set
// eslint-disable-next-line no-restricted-syntax
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])) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
returnData[key] = overwrites[key];
}
}

View file

@ -63,6 +63,7 @@ import {
NodeParameterValueType,
NodeExecutionWithMetadata,
IPairedItemData,
deepCopy,
} from 'n8n-workflow';
import { Agent } from 'https';
@ -1641,7 +1642,7 @@ export async function getCredentials(
*
*/
export function getNode(node: INode): INode {
return JSON.parse(JSON.stringify(node));
return deepCopy(node);
}
/**

View file

@ -16,6 +16,7 @@ import {
USER_SETTINGS_FILE_NAME,
USER_SETTINGS_SUBFOLDER,
} from '.';
import { deepCopy } from 'n8n-workflow';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { promisify } = require('util');
@ -173,7 +174,7 @@ export async function writeUserSettings(
}
await fsWriteFile(settingsPath, JSON.stringify(settingsToWrite, null, '\t'));
settingsCache = JSON.parse(JSON.stringify(userSettings));
settingsCache = deepCopy(userSettings);
return userSettings;
}

View file

@ -38,6 +38,7 @@ import {
} from '@/Interface';
import {
deepCopy,
INodeProperties,
INodePropertyOptions,
} from 'n8n-workflow';
@ -161,7 +162,7 @@ export default mixins(
} else {
// Everything else saves them directly as an array.
newValue = get(this.nodeValues, `${this.path}.${optionName}`, []);
newValue.push(JSON.parse(JSON.stringify(option.default)));
newValue.push(deepCopy(option.default));
}
parameterData = {
@ -172,7 +173,7 @@ export default mixins(
// Add a new option
parameterData = {
name,
value: JSON.parse(JSON.stringify(option.default)),
value: deepCopy(option.default),
};
}

View file

@ -113,8 +113,10 @@ import {
} from '@/Interface';
import {
deepCopy,
INodeParameters,
INodePropertyCollection,
NodeParameterValue,
} from 'n8n-workflow';
import { get } from 'lodash';
@ -249,13 +251,13 @@ export default mixins(genericHelpers)
// Multiple values are allowed so append option to array
newParameterValue[optionParameter.name] = get(this.nodeValues, `${this.path}.${optionParameter.name}`, []);
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') {
(newParameterValue[optionParameter.name] as INodeParameters[]).push(JSON.parse(JSON.stringify(optionParameter.default)));
(newParameterValue[optionParameter.name] as NodeParameterValue[]).push(deepCopy(optionParameter.default));
}
} else {
// Add a new option
newParameterValue[optionParameter.name] = JSON.parse(JSON.stringify(optionParameter.default));
newParameterValue[optionParameter.name] = deepCopy(optionParameter.default);
}
}

View file

@ -46,6 +46,7 @@ import { get } from 'lodash';
import { genericHelpers } from '@/components/mixins/genericHelpers';
import mixins from 'vue-typed-mixins';
import { deepCopy } from "n8n-workflow";
export default mixins(genericHelpers)
.extend({
@ -87,7 +88,7 @@ export default mixins(genericHelpers)
currentValue = [];
}
currentValue.push(JSON.parse(JSON.stringify(this.parameter.default)));
currentValue.push(deepCopy(this.parameter.default));
const parameterData = {
name,

View file

@ -122,6 +122,7 @@ import {
INodeProperties,
NodeHelpers,
NodeParameterValue,
deepCopy,
} from 'n8n-workflow';
import { INodeUi, INodeUpdatePropertiesInformation, IUpdateInformation } from '@/Interface';
@ -427,7 +428,7 @@ export default mixins(externalHooks, genericHelpers, nodeHelpers).extend({
// Value should be set
if (typeof value === 'object') {
// @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 {
// @ts-ignore
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
// we do not edit it directly
nodeParameters = JSON.parse(JSON.stringify(nodeParameters));
nodeParameters = deepCopy(nodeParameters);
for (const parameterName of Object.keys(parameterData.value)) {
//@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
// 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
// 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 {
this.nodeValid = false;
}

View file

@ -97,6 +97,7 @@
<script lang="ts">
import {
deepCopy,
INodeParameters,
INodeProperties,
INodeTypeDescription,
@ -288,7 +289,7 @@ export default mixins(
if (parameterGotResolved === true) {
if (this.path) {
rawValues = JSON.parse(JSON.stringify(this.nodeValues));
rawValues = deepCopy(this.nodeValues);
set(rawValues, this.path, nodeValues);
return this.displayParameter(rawValues, parameter, this.path, this.node);
} else {

View file

@ -192,6 +192,7 @@ import { WORKFLOW_SETTINGS_MODAL_KEY } from '../constants';
import mixins from 'vue-typed-mixins';
import { mapGetters } from "vuex";
import { deepCopy } from "n8n-workflow";
export default mixins(
externalHooks,
@ -274,7 +275,7 @@ export default mixins(
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) {
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);

View file

@ -31,6 +31,7 @@ import {
IExecuteData,
INodeConnection,
IWebhookDescription,
deepCopy,
} from 'n8n-workflow';
import {
@ -361,8 +362,8 @@ export const workflowHelpers = mixins(
cachedWorkflow = new Workflow({
id: workflowId,
name: workflowName,
nodes: copyData ? JSON.parse(JSON.stringify(nodes)) : nodes,
connections: copyData? JSON.parse(JSON.stringify(connections)): connections,
nodes: copyData ? deepCopy(nodes) : nodes,
connections: copyData? deepCopy(connections): connections,
active: false,
nodeTypes,
settings: this.$store.getters.workflowSettings,

View file

@ -99,6 +99,7 @@ import * as CanvasHelpers from './canvasHelpers';
import mixins from 'vue-typed-mixins';
import { v4 as uuid } from 'uuid';
import {
deepCopy,
IConnection,
IConnections,
IDataObject,
@ -459,7 +460,7 @@ export default mixins(
this.$store.commit('setWorkflowExecutionData', data);
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.zoomToFit();
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
// not share objects
const newNodeData = JSON.parse(JSON.stringify(this.getNodeDataToSave(node)));
const newNodeData = deepCopy(this.getNodeDataToSave(node));
newNodeData.id = uuid();
// Check if node-name is unique else find one that is

View file

@ -16,3 +16,4 @@ export * from './WorkflowDataProxy';
export * from './WorkflowErrors';
export * from './WorkflowHooks';
export { LoggerProxy, NodeHelpers, ObservableObject, TelemetryHelpers };
export { deepCopy } from './utils';

View 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);
});
});

View file

@ -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