import type { IDataObject, IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeBaseDescription, INodeTypeDescription, } from 'n8n-workflow'; import { NodeConnectionType } from 'n8n-workflow'; import type { IncludeMods, SetField, SetNodeOptions } from './helpers/interfaces'; import { INCLUDE } from './helpers/interfaces'; import * as raw from './raw.mode'; import * as manual from './manual.mode'; type Mode = 'manual' | 'raw'; const versionDescription: INodeTypeDescription = { displayName: 'Edit Fields (Set)', name: 'set', iconColor: 'blue', group: ['input'], version: [3, 3.1, 3.2, 3.3, 3.4], description: 'Modify, add, or remove item fields', subtitle: '={{$parameter["mode"]}}', defaults: { name: 'Edit Fields', }, inputs: [NodeConnectionType.Main], outputs: [NodeConnectionType.Main], properties: [ { displayName: 'Mode', name: 'mode', type: 'options', noDataExpression: true, options: [ { name: 'Manual Mapping', value: 'manual', description: 'Edit item fields one by one', action: 'Edit item fields one by one', }, { name: 'JSON', value: 'raw', description: 'Customize item output with JSON', action: 'Customize item output with JSON', }, ], default: 'manual', }, { displayName: 'Duplicate Item', name: 'duplicateItem', type: 'boolean', default: false, isNodeSetting: true, }, { displayName: 'Duplicate Item Count', name: 'duplicateCount', type: 'number', default: 0, typeOptions: { minValue: 0, }, description: 'How many times the item should be duplicated, mainly used for testing and debugging', isNodeSetting: true, displayOptions: { show: { duplicateItem: [true], }, }, }, { displayName: 'Item duplication is set in the node settings. This option will be ignored when the workflow runs automatically.', name: 'duplicateWarning', type: 'notice', default: '', displayOptions: { show: { duplicateItem: [true], }, }, }, ...raw.description, ...manual.description, { displayName: 'Include in Output', name: 'include', type: 'options', description: 'How to select the fields you want to include in your output items', default: 'all', displayOptions: { show: { '@version': [3, 3.1, 3.2], }, }, options: [ { name: 'All Input Fields', value: INCLUDE.ALL, description: 'Also include all unchanged fields from the input', }, { name: 'No Input Fields', value: INCLUDE.NONE, description: 'Include only the fields specified above', }, { name: 'Selected Input Fields', value: INCLUDE.SELECTED, description: 'Also include the fields listed in the parameter “Fields to Include”', }, { name: 'All Input Fields Except', value: INCLUDE.EXCEPT, description: 'Exclude the fields listed in the parameter “Fields to Exclude”', }, ], }, { displayName: 'Include Other Input Fields', name: 'includeOtherFields', type: 'boolean', default: false, description: "Whether to pass to the output all the input fields (along with the fields set in 'Fields to Set')", displayOptions: { hide: { '@version': [3, 3.1, 3.2], }, }, }, { displayName: 'Input Fields to Include', name: 'include', type: 'options', description: 'How to select the fields you want to include in your output items', default: 'all', displayOptions: { hide: { '@version': [3, 3.1, 3.2], '/includeOtherFields': [false], }, }, options: [ { name: 'All', value: INCLUDE.ALL, description: 'Also include all unchanged fields from the input', }, { name: 'Selected', value: INCLUDE.SELECTED, description: 'Also include the fields listed in the parameter “Fields to Include”', }, { name: 'All Except', value: INCLUDE.EXCEPT, description: 'Exclude the fields listed in the parameter “Fields to Exclude”', }, ], }, { displayName: 'Fields to Include', name: 'includeFields', type: 'string', default: '', placeholder: 'e.g. fieldToInclude1,fieldToInclude2', description: 'Comma-separated list of the field names you want to include in the output. You can drag the selected fields from the input panel.', requiresDataPath: 'multiple', displayOptions: { show: { include: ['selected'], }, }, }, { displayName: 'Fields to Exclude', name: 'excludeFields', type: 'string', default: '', placeholder: 'e.g. fieldToExclude1,fieldToExclude2', description: 'Comma-separated list of the field names you want to exclude from the output. You can drag the selected fields from the input panel.', requiresDataPath: 'multiple', displayOptions: { show: { include: ['except'], }, }, }, { displayName: 'Options', name: 'options', type: 'collection', placeholder: 'Add option', default: {}, options: [ { displayName: 'Include Binary File', name: 'includeBinary', type: 'boolean', default: true, displayOptions: { hide: { '@version': [{ _cnd: { gte: 3.4 } }], }, }, description: 'Whether binary data should be included if present in the input item', }, { displayName: 'Strip Binary Data', name: 'stripBinary', type: 'boolean', default: true, description: 'Whether binary data should be stripped from the input item. Only applies when "Include Other Input Fields" is enabled.', displayOptions: { show: { '@version': [{ _cnd: { gte: 3.4 } }], '/includeOtherFields': [true], }, }, }, { displayName: 'Ignore Type Conversion Errors', name: 'ignoreConversionErrors', type: 'boolean', default: false, description: 'Whether to ignore field type errors and apply a less strict type conversion', displayOptions: { show: { '/mode': ['manual'], }, }, }, { displayName: 'Support Dot Notation', name: 'dotNotation', type: 'boolean', default: true, // eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether description: 'By default, dot-notation is used in property names. This means that "a.b" will set the property "b" underneath "a" so { "a": { "b": value} }. If that is not intended this can be deactivated, it will then set { "a.b": value } instead.', }, ], }, ], }; export class SetV2 implements INodeType { description: INodeTypeDescription; constructor(baseDescription: INodeTypeBaseDescription) { this.description = { ...baseDescription, ...versionDescription, }; } async execute(this: IExecuteFunctions) { const items = this.getInputData(); const mode = this.getNodeParameter('mode', 0) as Mode; const duplicateItem = this.getNodeParameter('duplicateItem', 0, false) as boolean; const setNode = { raw, manual }; const returnData: INodeExecutionData[] = []; const rawData: IDataObject = {}; if (mode === 'raw') { const jsonOutput = this.getNodeParameter('jsonOutput', 0, '', { rawExpressions: true, }) as string | undefined; if (jsonOutput?.startsWith('=')) { rawData.jsonOutput = jsonOutput.replace(/^=+/, ''); } } else { const workflowFieldsJson = this.getNodeParameter('fields.values', 0, [], { rawExpressions: true, }) as SetField[]; for (const entry of workflowFieldsJson) { if (entry.type === 'objectValue' && (entry.objectValue as string).startsWith('=')) { rawData[entry.name] = (entry.objectValue as string).replace(/^=+/, ''); } } } for (let i = 0; i < items.length; i++) { const includeOtherFields = this.getNodeParameter('includeOtherFields', i, false) as boolean; const include = this.getNodeParameter('include', i, 'all') as IncludeMods; const options = this.getNodeParameter('options', i, {}); const node = this.getNode(); if (node.typeVersion >= 3.3) { options.include = includeOtherFields ? include : 'none'; } else { options.include = include; } const newItem = await setNode[mode].execute.call( this, items[i], i, options as SetNodeOptions, rawData, node, ); if (duplicateItem && this.getMode() === 'manual') { const duplicateCount = this.getNodeParameter('duplicateCount', 0, 0) as number; for (let j = 0; j <= duplicateCount; j++) { returnData.push(newItem); } } else { returnData.push(newItem); } } return [returnData]; } }