diff --git a/packages/@n8n_io/eslint-config/base.js b/packages/@n8n_io/eslint-config/base.js index 49bea0d344..7187bbf762 100644 --- a/packages/@n8n_io/eslint-config/base.js +++ b/packages/@n8n_io/eslint-config/base.js @@ -323,8 +323,7 @@ const config = (module.exports = { // TODO: set to `error` and fix offenses 'n8n-local-rules/no-uncaught-json-parse': 'warn', - // TODO: set to `error` and fix offenses - 'n8n-local-rules/no-json-parse-json-stringify': 'warn', + 'n8n-local-rules/no-json-parse-json-stringify': 'error', // ****************************************************************** // overrides to base ruleset diff --git a/packages/nodes-base/nodes/Amqp/AmqpTrigger.node.ts b/packages/nodes-base/nodes/Amqp/AmqpTrigger.node.ts index 19460825f8..999e0c6577 100644 --- a/packages/nodes-base/nodes/Amqp/AmqpTrigger.node.ts +++ b/packages/nodes-base/nodes/Amqp/AmqpTrigger.node.ts @@ -2,6 +2,7 @@ import { ContainerOptions, create_container, EventContext, Message, ReceiverOpti import { ITriggerFunctions } from 'n8n-core'; import { + deepCopy, IDataObject, INodeType, INodeTypeDescription, @@ -172,20 +173,20 @@ export class AmqpTrigger implements INodeType { if (options.jsonConvertByteArrayToString === true && data.body.content !== undefined) { // The buffer is not ready... Stringify and parse back to load it. - const cont = JSON.stringify(data.body.content); - data.body = String.fromCharCode.apply(null, JSON.parse(cont).data); + const cont = deepCopy(data.body.content); + data.body = String.fromCharCode.apply(null, cont.data); } if (options.jsonConvertByteArrayToString === true && data.body.content !== undefined) { // The buffer is not ready... Stringify and parse back to load it. - const cont = JSON.stringify(data.body.content); - data.body = String.fromCharCode.apply(null, JSON.parse(cont).data); + const cont = deepCopy(data.body.content); + data.body = String.fromCharCode.apply(null, cont.data); } if (options.jsonConvertByteArrayToString === true && data.body.content !== undefined) { // The buffer is not ready... Stringify and parse back to load it. - const content = JSON.stringify(data.body.content); - data.body = String.fromCharCode.apply(null, JSON.parse(content).data); + const content = deepCopy(data.body.content); + data.body = String.fromCharCode.apply(null, content.data); } if (options.jsonParseBody === true) { diff --git a/packages/nodes-base/nodes/Aws/DynamoDB/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/DynamoDB/GenericFunctions.ts index eb25667600..973fb2522d 100644 --- a/packages/nodes-base/nodes/Aws/DynamoDB/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/DynamoDB/GenericFunctions.ts @@ -6,6 +6,7 @@ import { } from 'n8n-core'; import { + deepCopy, ICredentialDataDecryptedObject, IDataObject, IHttpRequestOptions, @@ -93,7 +94,7 @@ export function copyInputItem(item: INodeExecutionData, properties: string[]): I if (item.json[property] === undefined) { newItem[property] = null; } else { - newItem[property] = JSON.parse(JSON.stringify(item.json[property])); + newItem[property] = deepCopy(item.json[property]); } } return newItem; diff --git a/packages/nodes-base/nodes/Aws/DynamoDB/utils.ts b/packages/nodes-base/nodes/Aws/DynamoDB/utils.ts index 35399172b1..61b9e36080 100644 --- a/packages/nodes-base/nodes/Aws/DynamoDB/utils.ts +++ b/packages/nodes-base/nodes/Aws/DynamoDB/utils.ts @@ -1,4 +1,4 @@ -import { IDataObject, INodeExecutionData } from 'n8n-workflow'; +import { deepCopy, IDataObject, INodeExecutionData } from 'n8n-workflow'; import { AdjustedPutItem, @@ -103,7 +103,7 @@ export function copyInputItem(item: INodeExecutionData, properties: string[]): I if (item.json[property] === undefined) { newItem[property] = null; } else { - newItem[property] = JSON.parse(JSON.stringify(item.json[property])); + newItem[property] = deepCopy(item.json[property]); } } return newItem; diff --git a/packages/nodes-base/nodes/Crypto/Crypto.node.ts b/packages/nodes-base/nodes/Crypto/Crypto.node.ts index 32ffb2647e..b254f05c63 100644 --- a/packages/nodes-base/nodes/Crypto/Crypto.node.ts +++ b/packages/nodes-base/nodes/Crypto/Crypto.node.ts @@ -3,6 +3,7 @@ import { set } from 'lodash'; import { IExecuteFunctions } from 'n8n-core'; import { + deepCopy, ILoadOptionsFunctions, INodeExecutionData, INodePropertyOptions, @@ -483,7 +484,7 @@ export class Crypto implements INodeType { if (dataPropertyName.includes('.')) { // Uses dot notation so copy all data newItem = { - json: JSON.parse(JSON.stringify(item.json)), + json: deepCopy(item.json), pairedItem: { item: i, }, diff --git a/packages/nodes-base/nodes/DateTime/DateTime.node.ts b/packages/nodes-base/nodes/DateTime/DateTime.node.ts index 052dfc5ae7..7c58f04436 100644 --- a/packages/nodes-base/nodes/DateTime/DateTime.node.ts +++ b/packages/nodes-base/nodes/DateTime/DateTime.node.ts @@ -1,6 +1,7 @@ import { IExecuteFunctions } from 'n8n-core'; import { + deepCopy, IDataObject, ILoadOptionsFunctions, INodeExecutionData, @@ -431,7 +432,7 @@ export class DateTime implements INodeType { if (dataPropertyName.includes('.')) { // Uses dot notation so copy all data newItem = { - json: JSON.parse(JSON.stringify(item.json)), + json: deepCopy(item.json), pairedItem: { item: i, }, @@ -475,7 +476,7 @@ export class DateTime implements INodeType { if (dataPropertyName.includes('.')) { // Uses dot notation so copy all data newItem = { - json: JSON.parse(JSON.stringify(item.json)), + json: deepCopy(item.json), pairedItem: { item: i, }, diff --git a/packages/nodes-base/nodes/EditImage/EditImage.node.ts b/packages/nodes-base/nodes/EditImage/EditImage.node.ts index ea2412701e..40fe251aad 100644 --- a/packages/nodes-base/nodes/EditImage/EditImage.node.ts +++ b/packages/nodes-base/nodes/EditImage/EditImage.node.ts @@ -1,5 +1,6 @@ import { BINARY_ENCODING, IExecuteFunctions } from 'n8n-core'; import { + deepCopy, IDataObject, ILoadOptionsFunctions, INodeExecutionData, @@ -1269,8 +1270,8 @@ export class EditImage implements INodeType { Object.assign(newItem.binary, item.binary); // Make a deep copy of the binary data we change if (newItem.binary![dataPropertyName as string]) { - newItem.binary![dataPropertyName as string] = JSON.parse( - JSON.stringify(newItem.binary![dataPropertyName as string]), + newItem.binary![dataPropertyName as string] = deepCopy( + newItem.binary![dataPropertyName as string], ); } } diff --git a/packages/nodes-base/nodes/Function/Function.node.ts b/packages/nodes-base/nodes/Function/Function.node.ts index 1b58ba7214..73645c7195 100644 --- a/packages/nodes-base/nodes/Function/Function.node.ts +++ b/packages/nodes-base/nodes/Function/Function.node.ts @@ -1,5 +1,6 @@ import { IExecuteFunctions } from 'n8n-core'; import { + deepCopy, IBinaryKeyData, IDataObject, INodeExecutionData, @@ -67,7 +68,7 @@ return items;`, let items = this.getInputData(); // Copy the items as they may get changed in the functions - items = JSON.parse(JSON.stringify(items)); + items = deepCopy(items); // Assign item indexes for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { @@ -82,7 +83,7 @@ return items;`, inputData[key] = cleanupData(inputData[key] as IDataObject); } else { // Is some special object like a Date so stringify - inputData[key] = JSON.parse(JSON.stringify(inputData[key])); + inputData[key] = deepCopy(inputData[key]); } } }); diff --git a/packages/nodes-base/nodes/FunctionItem/FunctionItem.node.ts b/packages/nodes-base/nodes/FunctionItem/FunctionItem.node.ts index 759b09ffbd..4527fc59c6 100644 --- a/packages/nodes-base/nodes/FunctionItem/FunctionItem.node.ts +++ b/packages/nodes-base/nodes/FunctionItem/FunctionItem.node.ts @@ -1,5 +1,6 @@ import { IExecuteFunctions } from 'n8n-core'; import { + deepCopy, IBinaryKeyData, IDataObject, INodeExecutionData, @@ -74,7 +75,7 @@ return item;`, inputData[key] = cleanupData(inputData[key] as IDataObject); } else { // Is some special object like a Date so stringify - inputData[key] = JSON.parse(JSON.stringify(inputData[key])); + inputData[key] = deepCopy(inputData[key]); } } }); @@ -89,7 +90,7 @@ return item;`, item.index = itemIndex; // Copy the items as they may get changed in the functions - item = JSON.parse(JSON.stringify(item)); + item = deepCopy(item); // Define the global objects for the custom function const sandbox = { diff --git a/packages/nodes-base/nodes/Markdown/Markdown.node.ts b/packages/nodes-base/nodes/Markdown/Markdown.node.ts index 3b65baf38c..9650d5aa0e 100644 --- a/packages/nodes-base/nodes/Markdown/Markdown.node.ts +++ b/packages/nodes-base/nodes/Markdown/Markdown.node.ts @@ -1,6 +1,7 @@ import { IExecuteFunctions } from 'n8n-core'; import { + deepCopy, IDataObject, INodeExecutionData, INodeType, @@ -590,7 +591,7 @@ export class Markdown implements INodeType { const markdownFromHTML = NodeHtmlMarkdown.translate(html, markdownOptions); - const newItem = JSON.parse(JSON.stringify(items[i].json)); + const newItem = deepCopy(items[i].json); set(newItem, destinationKey, markdownFromHTML); returnData.push(newItem); } @@ -605,7 +606,7 @@ export class Markdown implements INodeType { Object.keys(options).forEach((key) => converter.setOption(key, options[key])); const htmlFromMarkdown = converter.makeHtml(markdown); - const newItem = JSON.parse(JSON.stringify(items[i].json)); + const newItem = deepCopy(items[i].json); set(newItem, destinationKey, htmlFromMarkdown); returnData.push(newItem); diff --git a/packages/nodes-base/nodes/Merge/v1/MergeV1.node.ts b/packages/nodes-base/nodes/Merge/v1/MergeV1.node.ts index 26ec817648..d5bb9de2f2 100644 --- a/packages/nodes-base/nodes/Merge/v1/MergeV1.node.ts +++ b/packages/nodes-base/nodes/Merge/v1/MergeV1.node.ts @@ -4,6 +4,7 @@ import { get } from 'lodash'; import { IExecuteFunctions } from 'n8n-core'; import { + deepCopy, GenericValue, INodeExecutionData, INodeType, @@ -427,7 +428,7 @@ export class MergeV1 implements INodeType { continue; } else if (mode === 'mergeByKey') { // Copy the entry as the data gets changed - entry = JSON.parse(JSON.stringify(entry)); + entry = deepCopy(entry); for (key of Object.keys(copyData[referenceValue as string].json)) { if (key === propertyName2) { diff --git a/packages/nodes-base/nodes/Microsoft/Sql/GenericFunctions.ts b/packages/nodes-base/nodes/Microsoft/Sql/GenericFunctions.ts index e61f5d3bb4..c222c7149d 100644 --- a/packages/nodes-base/nodes/Microsoft/Sql/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Microsoft/Sql/GenericFunctions.ts @@ -1,4 +1,4 @@ -import { IDataObject, INodeExecutionData } from 'n8n-workflow'; +import { deepCopy, IDataObject, INodeExecutionData } from 'n8n-workflow'; import { ITables } from './TableInterface'; /** @@ -15,7 +15,7 @@ export function copyInputItem(item: INodeExecutionData, properties: string[]): I if (item.json[property] === undefined) { newItem[property] = null; } else { - newItem[property] = JSON.parse(JSON.stringify(item.json[property])); + newItem[property] = deepCopy(item.json[property]); } } return newItem; diff --git a/packages/nodes-base/nodes/MoveBinaryData/MoveBinaryData.node.ts b/packages/nodes-base/nodes/MoveBinaryData/MoveBinaryData.node.ts index 120dc7d3ca..e3b2c106a4 100644 --- a/packages/nodes-base/nodes/MoveBinaryData/MoveBinaryData.node.ts +++ b/packages/nodes-base/nodes/MoveBinaryData/MoveBinaryData.node.ts @@ -4,6 +4,7 @@ import { BINARY_ENCODING } from 'n8n-core'; import { IExecuteFunctions } from 'n8n-core'; import { + deepCopy, IBinaryData, IDataObject, INodeExecutionData, @@ -364,7 +365,7 @@ export class MoveBinaryData implements INodeType { newItem.json = JSON.parse(convertedValue); } else { // Does get added to existing data so copy it first - newItem.json = JSON.parse(JSON.stringify(item.json)); + newItem.json = deepCopy(item.json); if (options.keepAsBase64 !== true) { convertedValue = iconv.decode(buffer, encoding, { @@ -387,7 +388,7 @@ export class MoveBinaryData implements INodeType { newItem.binary = item.binary; } else { // Binary data will change so copy it - newItem.binary = JSON.parse(JSON.stringify(item.binary)); + newItem.binary = deepCopy(item.binary); unset(newItem.binary, sourceKey); } } else if (mode === 'jsonToBinary') { @@ -408,7 +409,7 @@ export class MoveBinaryData implements INodeType { if (item.binary !== undefined) { // Item already has binary data so copy it - newItem.binary = JSON.parse(JSON.stringify(item.binary)); + newItem.binary = deepCopy(item.binary); } else { // Item does not have binary data yet so initialize empty newItem.binary = {}; @@ -447,7 +448,7 @@ export class MoveBinaryData implements INodeType { } else { // Data should not be kept and only one key has to get removed. So copy all // data and then remove the not needed one - newItem.json = JSON.parse(JSON.stringify(item.json)); + newItem.json = deepCopy(item.json); const sourceKey = this.getNodeParameter('sourceKey', itemIndex) as string; unset(newItem.json, sourceKey); diff --git a/packages/nodes-base/nodes/MySql/GenericFunctions.ts b/packages/nodes-base/nodes/MySql/GenericFunctions.ts index bd49674e82..4702c851f6 100644 --- a/packages/nodes-base/nodes/MySql/GenericFunctions.ts +++ b/packages/nodes-base/nodes/MySql/GenericFunctions.ts @@ -1,4 +1,11 @@ -import { ICredentialDataDecryptedObject, IDataObject, ILoadOptionsFunctions, INodeExecutionData, INodeListSearchResult } from 'n8n-workflow'; +import { + deepCopy, + ICredentialDataDecryptedObject, + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodeListSearchResult, +} from 'n8n-workflow'; import mysql2 from 'mysql2/promise'; /** @@ -17,14 +24,16 @@ export function copyInputItems(items: INodeExecutionData[], properties: string[] if (item.json[property] === undefined) { newItem[property] = null; } else { - newItem[property] = JSON.parse(JSON.stringify(item.json[property])); + newItem[property] = deepCopy(item.json[property]); } } return newItem; }); } -export function createConnection(credentials: ICredentialDataDecryptedObject): Promise { +export function createConnection( + credentials: ICredentialDataDecryptedObject, +): Promise { const { ssl, caCertificate, clientCertificate, clientPrivateKey, ...baseCredentials } = credentials; @@ -57,7 +66,7 @@ export async function searchTables( ORDER BY table_name `; const [rows] = await connection.query(sql); - const results = (rows as IDataObject[]).map(r => ({ + const results = (rows as IDataObject[]).map((r) => ({ name: r.TABLE_NAME as string, value: r.TABLE_NAME as string, })); diff --git a/packages/nodes-base/nodes/Odoo/Odoo.node.ts b/packages/nodes-base/nodes/Odoo/Odoo.node.ts index f8387834db..0d7c4cb2dd 100644 --- a/packages/nodes-base/nodes/Odoo/Odoo.node.ts +++ b/packages/nodes-base/nodes/Odoo/Odoo.node.ts @@ -2,6 +2,7 @@ import { IExecuteFunctions } from 'n8n-core'; import { OptionsWithUri } from 'request'; import { + deepCopy, ICredentialsDecrypted, ICredentialTestFunctions, IDataObject, @@ -297,7 +298,7 @@ export class Odoo implements INodeType { async execute(this: IExecuteFunctions): Promise { let items = this.getInputData(); - items = JSON.parse(JSON.stringify(items)); + items = deepCopy(items); const returnData: IDataObject[] = []; let responseData; diff --git a/packages/nodes-base/nodes/RenameKeys/RenameKeys.node.ts b/packages/nodes-base/nodes/RenameKeys/RenameKeys.node.ts index ffc9ee496f..823706953f 100644 --- a/packages/nodes-base/nodes/RenameKeys/RenameKeys.node.ts +++ b/packages/nodes-base/nodes/RenameKeys/RenameKeys.node.ts @@ -1,5 +1,11 @@ import { IExecuteFunctions } from 'n8n-core'; -import { IDataObject, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow'; +import { + deepCopy, + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; import { get, set, unset } from 'lodash'; import { options } from 'rhea'; @@ -165,7 +171,7 @@ export class RenameKeys implements INodeType { // Copy the whole JSON data as data on any level can be renamed newItem = { - json: JSON.parse(JSON.stringify(item.json)), + json: deepCopy(item.json), pairedItem: { item: itemIndex, }, diff --git a/packages/nodes-base/nodes/Set/Set.node.ts b/packages/nodes-base/nodes/Set/Set.node.ts index 3055f7b177..87ef394f7d 100644 --- a/packages/nodes-base/nodes/Set/Set.node.ts +++ b/packages/nodes-base/nodes/Set/Set.node.ts @@ -1,5 +1,6 @@ import { IExecuteFunctions } from 'n8n-core'; import { + deepCopy, IDataObject, INodeExecutionData, INodeParameters, @@ -162,7 +163,7 @@ export class Set implements INodeType { Object.assign(newItem.binary, item.binary); } - newItem.json = JSON.parse(JSON.stringify(item.json)); + newItem.json = deepCopy(item.json); } // Add boolean values diff --git a/packages/nodes-base/nodes/Snowflake/GenericFunctions.ts b/packages/nodes-base/nodes/Snowflake/GenericFunctions.ts index 364255848c..accb3c1cbd 100644 --- a/packages/nodes-base/nodes/Snowflake/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Snowflake/GenericFunctions.ts @@ -1,4 +1,4 @@ -import { IDataObject, INodeExecutionData } from 'n8n-workflow'; +import { deepCopy, IDataObject, INodeExecutionData } from 'n8n-workflow'; import snowflake from 'snowflake-sdk'; @@ -53,7 +53,7 @@ export function copyInputItems(items: INodeExecutionData[], properties: string[] if (item.json[property] === undefined) { newItem[property] = null; } else { - newItem[property] = JSON.parse(JSON.stringify(item.json[property])); + newItem[property] = deepCopy(item.json[property]); } } return newItem; diff --git a/packages/nodes-base/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.ts b/packages/nodes-base/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.ts index 6a7a603a1b..63321a6ac6 100644 --- a/packages/nodes-base/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.ts +++ b/packages/nodes-base/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.ts @@ -1,6 +1,7 @@ import { IHookFunctions, IWebhookFunctions } from 'n8n-core'; import { + deepCopy, IDataObject, ILoadOptionsFunctions, INodeExecutionData, @@ -707,7 +708,7 @@ export class SurveyMonkeyTrigger implements INodeType { responseData.questions = {}; // Map the "Map" to JSON - const tuples = JSON.parse(JSON.stringify([...responseQuestions])); + const tuples = deepCopy([...responseQuestions]); for (const [key, value] of tuples) { responseData.questions[key] = value; } diff --git a/packages/nodes-base/nodes/UProc/ToolDescription.ts b/packages/nodes-base/nodes/UProc/ToolDescription.ts index 7e19a31145..463ff687b8 100644 --- a/packages/nodes-base/nodes/UProc/ToolDescription.ts +++ b/packages/nodes-base/nodes/UProc/ToolDescription.ts @@ -1,4 +1,4 @@ -import { IDataObject, INodeProperties } from 'n8n-workflow'; +import { deepCopy, IDataObject, INodeProperties } from 'n8n-workflow'; import { groups } from './Json/Groups'; @@ -81,7 +81,7 @@ for (const tool of (tools as IDataObject).processors as IDataObject[]) { tool: [tool.k], }, }, - description: JSON.parse(JSON.stringify(description)), + description: deepCopy(description), }; let modifiedParam = null; @@ -109,6 +109,7 @@ for (const tool of (tools as IDataObject).processors as IDataObject[]) { newParameters.push(currentParam); } } + // eslint-disable-next-line n8n-local-rules/no-json-parse-json-stringify parameters = JSON.parse(JSON.stringify(newParameters)); } else { parameters.push(parameter); diff --git a/packages/workflow/src/NodeHelpers.ts b/packages/workflow/src/NodeHelpers.ts index 8e271528e3..d41dc9bbbd 100644 --- a/packages/workflow/src/NodeHelpers.ts +++ b/packages/workflow/src/NodeHelpers.ts @@ -38,6 +38,7 @@ import { NodeParameterValue, WebhookHttpMethod, } from './Interfaces'; +import { deepCopy } from './utils'; import type { Workflow } from './Workflow'; @@ -660,9 +661,7 @@ export function getNodeParameters( } else if (returnDefaults) { // Does not have values defined but defaults should be returned if (Array.isArray(nodeProperties.default)) { - nodeParameters[nodeProperties.name] = JSON.parse( - JSON.stringify(nodeProperties.default), - ); + nodeParameters[nodeProperties.name] = deepCopy(nodeProperties.default); } else { // As it is probably wrong for many nodes, do we keep on returning an empty array if // anything else than an array is set as default @@ -690,7 +689,7 @@ export function getNodeParameters( } } else if (returnDefaults) { // Does not have values defined but defaults should be returned - nodeParameters[nodeProperties.name] = JSON.parse(JSON.stringify(nodeProperties.default)); + nodeParameters[nodeProperties.name] = deepCopy(nodeProperties.default); nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name]; } } else if (nodeProperties.type === 'fixedCollection') { @@ -704,7 +703,7 @@ export function getNodeParameters( let propertyValues = nodeValues[nodeProperties.name]; if (returnDefaults) { if (propertyValues === undefined) { - propertyValues = JSON.parse(JSON.stringify(nodeProperties.default)); + propertyValues = deepCopy(nodeProperties.default); } } @@ -819,9 +818,7 @@ export function getNodeParameters( if (returnDefaults) { // Set also when it has the default value if (collectionValues === undefined) { - nodeParameters[nodeProperties.name] = JSON.parse( - JSON.stringify(nodeProperties.default), - ); + nodeParameters[nodeProperties.name] = deepCopy(nodeProperties.default); } else { nodeParameters[nodeProperties.name] = collectionValues; }