diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index cac599b5ca..ca1ea6028b 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -33,6 +33,7 @@ import { Agent, type AgentOptions } from 'https'; import get from 'lodash/get'; import isEmpty from 'lodash/isEmpty'; import pick from 'lodash/pick'; +import { DateTime } from 'luxon'; import { extension, lookup } from 'mime-types'; import type { BinaryHelperFunctions, @@ -2096,7 +2097,7 @@ export async function getCredentials( * Clean up parameter data to make sure that only valid data gets returned * INFO: Currently only converts Luxon Dates as we know for sure it will not be breaking */ -function cleanupParameterData(inputData: NodeParameterValueType): void { +export function cleanupParameterData(inputData: NodeParameterValueType): void { if (typeof inputData !== 'object' || inputData === null) { return; } @@ -2107,14 +2108,15 @@ function cleanupParameterData(inputData: NodeParameterValueType): void { } if (typeof inputData === 'object') { - Object.keys(inputData).forEach((key) => { - if (typeof inputData[key as keyof typeof inputData] === 'object') { - if (inputData[key as keyof typeof inputData]?.constructor.name === 'DateTime') { + type Key = keyof typeof inputData; + (Object.keys(inputData) as Key[]).forEach((key) => { + const value = inputData[key]; + if (typeof value === 'object') { + if (value instanceof DateTime) { // Is a special luxon date so convert to string - inputData[key as keyof typeof inputData] = - inputData[key as keyof typeof inputData]?.toString(); + inputData[key] = value.toString(); } else { - cleanupParameterData(inputData[key as keyof typeof inputData]); + cleanupParameterData(value); } } }); diff --git a/packages/core/test/NodeExecuteFunctions.test.ts b/packages/core/test/NodeExecuteFunctions.test.ts index 13791d126c..a644cd6793 100644 --- a/packages/core/test/NodeExecuteFunctions.test.ts +++ b/packages/core/test/NodeExecuteFunctions.test.ts @@ -1,4 +1,5 @@ import { + cleanupParameterData, copyInputItems, getBinaryDataBuffer, parseIncomingMessage, @@ -7,6 +8,7 @@ import { removeEmptyBody, setBinaryDataBuffer, } from '@/NodeExecuteFunctions'; +import { DateTime } from 'luxon'; import { mkdtempSync, readFileSync } from 'fs'; import type { IncomingMessage } from 'http'; import { mock } from 'jest-mock-extended'; @@ -18,6 +20,7 @@ import type { IRequestOptions, ITaskDataConnections, IWorkflowExecuteAdditionalData, + NodeParameterValue, Workflow, WorkflowHooks, } from 'n8n-workflow'; @@ -414,6 +417,29 @@ describe('NodeExecuteFunctions', () => { }); }); + describe('cleanupParameterData', () => { + it('should stringify Luxon dates in-place', () => { + const input = { x: 1, y: DateTime.now() as unknown as NodeParameterValue }; + expect(typeof input.y).toBe('object'); + cleanupParameterData(input); + expect(typeof input.y).toBe('string'); + }); + + it('should handle objects with nameless constructors', () => { + const input = { x: 1, y: { constructor: {} } as NodeParameterValue }; + expect(typeof input.y).toBe('object'); + cleanupParameterData(input); + expect(typeof input.y).toBe('object'); + }); + + it('should handle objects without a constructor', () => { + const input = { x: 1, y: { constructor: undefined } as unknown as NodeParameterValue }; + expect(typeof input.y).toBe('object'); + cleanupParameterData(input); + expect(typeof input.y).toBe('object'); + }); + }); + describe('copyInputItems', () => { it('should pick only selected properties', () => { const output = copyInputItems(