first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
{
"node": "n8n-nodes-base.set",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Core Nodes"],
"resources": {
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/"
}
],
"generic": [
{
"label": "Learn to Automate Your Factory's Incident Reporting: A Step by Step Guide",
"icon": "🏭",
"url": "https://n8n.io/blog/learn-to-automate-your-factorys-incident-reporting-a-step-by-step-guide/"
},
{
"label": "2021: The Year to Automate the New You with n8n",
"icon": "☀️",
"url": "https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/"
},
{
"label": "Automatically pulling and visualizing data with n8n",
"icon": "📈",
"url": "https://n8n.io/blog/automatically-pulling-and-visualizing-data-with-n8n/"
},
{
"label": "Database Monitoring and Alerting with n8n",
"icon": "📡",
"url": "https://n8n.io/blog/database-monitoring-and-alerting-with-n8n/"
},
{
"label": "Automatically Adding Expense Receipts to Google Sheets with Telegram, Mindee, Twilio, and n8n",
"icon": "🧾",
"url": "https://n8n.io/blog/automatically-adding-expense-receipts-to-google-sheets-with-telegram-mindee-twilio-and-n8n/"
},
{
"label": "6 e-commerce workflows to power up your Shopify s",
"icon": "store",
"url": "https://n8n.io/blog/no-code-ecommerce-workflow-automations/"
},
{
"label": "How to build a low-code, self-hosted URL shortener in 3 steps",
"icon": "🔗",
"url": "https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/"
},
{
"label": "Automate your data processing pipeline in 9 steps",
"icon": "⚙️",
"url": "https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/"
},
{
"label": "How to get started with CRM automation (with 3 no-code workflow ideas",
"icon": "👥",
"url": "https://n8n.io/blog/how-to-get-started-with-crm-automation-and-no-code-workflow-ideas/"
},
{
"label": "5 tasks you can automate with the new Notion API ",
"icon": "⚡️",
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
},
{
"label": "15 Google apps you can combine and automate to increase productivity",
"icon": "💡",
"url": "https://n8n.io/blog/automate-google-apps-for-productivity/"
},
{
"label": "How uProc scraped a multi-page website with a low-code workflow",
"icon": " 🕸️",
"url": "https://n8n.io/blog/how-uproc-scraped-a-multi-page-website-with-a-low-code-workflow/"
},
{
"label": "Building an expense tracking app in 10 minutes",
"icon": "📱",
"url": "https://n8n.io/blog/building-an-expense-tracking-app-in-10-minutes/"
},
{
"label": "The ultimate guide to automate your video collaboration with Whereby, Mattermost, and n8n",
"icon": "📹",
"url": "https://n8n.io/blog/the-ultimate-guide-to-automate-your-video-collaboration-with-whereby-mattermost-and-n8n/"
},
{
"label": "5 workflow automations for Mattermost that we love at n8n",
"icon": "🤖",
"url": "https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/"
},
{
"label": "Learn to Build Powerful API Endpoints Using Webhooks",
"icon": "🧰",
"url": "https://n8n.io/blog/learn-to-build-powerful-api-endpoints-using-webhooks/"
},
{
"label": "How a Membership Development Manager automates his work and investments",
"icon": "📈",
"url": "https://n8n.io/blog/how-a-membership-development-manager-automates-his-work-and-investments/"
},
{
"label": "A low-code bitcoin ticker built with QuestDB and n8n.io",
"icon": "📈",
"url": "https://n8n.io/blog/a-low-code-bitcoin-ticker-built-with-questdb-and-n8n-io/"
},
{
"label": "How to set up a no-code CI/CD pipeline with GitHub and TravisCI",
"icon": "🎡",
"url": "https://n8n.io/blog/how-to-set-up-a-ci-cd-pipeline-with-no-code/"
},
{
"label": "Benefits of automation and n8n: An interview with HubSpot's Hugh Durkin",
"icon": "🎖",
"url": "https://n8n.io/blog/benefits-of-automation-and-n8n-an-interview-with-hubspots-hugh-durkin/"
},
{
"label": "How Goomer automated their operations with over 200 n8n workflows",
"icon": "🛵",
"url": "https://n8n.io/blog/how-goomer-automated-their-operations-with-over-200-n8n-workflows/"
},
{
"label": "7 no-code workflow automations for Amazon Web Services",
"url": "https://n8n.io/blog/aws-workflow-automation/"
}
]
},
"alias": ["Set", "JS", "JSON", "Filter", "Transform", "Map"],
"subcategories": {
"Core Nodes": ["Data Transformation"]
}
}
+30
View File
@@ -0,0 +1,30 @@
import type { INodeTypeBaseDescription, IVersionedNodeType } from 'n8n-workflow';
import { VersionedNodeType } from 'n8n-workflow';
import { SetV1 } from './v1/SetV1.node';
import { SetV2 } from './v2/SetV2.node';
export class Set extends VersionedNodeType {
constructor() {
const baseDescription: INodeTypeBaseDescription = {
displayName: 'Set',
name: 'set',
icon: 'fa:pen',
group: ['input'],
description: 'Add or edit fields on an input item and optionally remove other fields',
defaultVersion: 3.4,
};
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
1: new SetV1(baseDescription),
2: new SetV1(baseDescription),
3: new SetV2(baseDescription),
3.1: new SetV2(baseDescription),
3.2: new SetV2(baseDescription),
3.3: new SetV2(baseDescription),
3.4: new SetV2(baseDescription),
};
super(nodeVersions, baseDescription);
}
}
@@ -0,0 +1,5 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
describe('Test Set Node', () => {
new NodeTestHarness().setupTests();
});
@@ -0,0 +1,854 @@
{
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "27cc9b56542ad45b38725555722c50a1c3fee1670bbb67980558314ee08517c4"
},
"nodes": [
{
"parameters": {
"fields": {
"values": [
{
"name": "test",
"stringValue": "foo"
}
]
},
"include": "none",
"options": {}
},
"id": "d425fb32-2bbc-445d-8fec-0e1288adf6d8",
"name": "Keep None",
"type": "n8n-nodes-base.set",
"typeVersion": 3,
"position": [800, 2680]
},
{
"parameters": {},
"id": "82a50bee-4dd1-4bb0-9513-71a54d37b51f",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [160, 1460]
},
{
"parameters": {
"data": [
{
"string": {
"test2": "hello",
"test3": " ",
"test4": "",
"test5": "3",
"test6": "3,14",
"test7": "3.14",
"test8": "TRUE",
"test9": "false",
"test10": "1",
"test11": "[\"apples\", \"oranges\"]",
"test12": "\"apples\", \"oranges\"",
"test13": "[1, 2]",
"test14": "{\"a\": 1, \"b\": { \"c\": 10, \"d\": \"test\"}}",
"test15": "{\"a\": 1}",
"test16": "null",
"test17": "undefined",
"test18": "0"
},
"number": {
"test1": 52472,
"test2": -1,
"test3": 0,
"test4": 1.334535,
"test5": null,
"test7": 1
},
"boolean": {
"test3": true,
"test4": false
},
"date": {
"test1": "2024-01-01T00:00:00.000Z",
"test2": "2023-08-01T12:34:56Z",
"test3": "2016-05-25T09:24:15.123",
"test4": "Tue, 01 Nov 2016 13:23:12 +0630",
"test5": "2017-05-15 09:24:15",
"test6": "1542674993",
"test7": 1542674993
},
"array": {
"test13": [1, 2, 3, 4]
},
"object": {
"obj": {
"objKey": 2,
"objArray": [1, 2, 3, 4],
"objBool": true
}
}
}
]
},
"id": "70c10657-5d81-46c5-b931-966024d7ce45",
"name": "Code1",
"type": "n8n-nodes-testing.testData",
"typeVersion": 1,
"position": [640, 1460]
},
{
"parameters": {
"fields": {
"values": [
{
"name": "numberToString1",
"stringValue": "={{ $json.number.test1 }}"
},
{
"name": "numberToString2",
"stringValue": "={{ $json.number.test2 }}"
},
{
"name": "numberToString3",
"stringValue": "={{ $json.number.test4 }}"
},
{
"name": "boolToString1",
"stringValue": "={{ $json.boolean.test3 }}"
},
{
"name": "boolToString2",
"stringValue": "={{ $json.boolean.test4 }}"
},
{
"name": "arrayToString1",
"stringValue": "={{ $json.array.test13 }}"
},
{
"name": "objectToString1",
"stringValue": "={{ $json.object.obj }}"
}
]
},
"include": "none",
"options": {}
},
"id": "4c53fe8b-f01f-496e-83cd-e89070ed8ced",
"name": "To String",
"type": "n8n-nodes-base.set",
"typeVersion": 3,
"position": [1000, 1080]
},
{
"parameters": {
"fields": {
"values": [
{
"name": "stringToNumber1",
"type": "numberValue",
"numberValue": "={{ $json.string.test5 }}"
},
{
"name": "stringToNumber2",
"type": "numberValue",
"numberValue": "={{ $json.string.test7 }}"
},
{
"name": "boolToNumber1",
"type": "numberValue",
"numberValue": "={{ $json.boolean.test3 }}"
},
{
"name": "boolToNumber2",
"type": "numberValue",
"numberValue": "={{ $json.boolean.test4 }}"
}
]
},
"include": "none",
"options": {}
},
"id": "d6c951b9-4b55-46cc-945a-d6f8f82d157a",
"name": "To Number",
"type": "n8n-nodes-base.set",
"typeVersion": 3,
"position": [1000, 1280]
},
{
"parameters": {
"fields": {
"values": [
{
"name": "stringToBoolean1",
"type": "booleanValue",
"booleanValue": "={{ $json.string.test8 }}"
},
{
"name": "stringToBoolean3",
"type": "booleanValue",
"booleanValue": "={{ $json.string.test9 }}"
},
{
"name": "stringToBoolean4",
"type": "booleanValue",
"booleanValue": "={{ $json.string.test10 }}"
},
{
"name": "stringToBoolean5",
"type": "booleanValue",
"booleanValue": "={{ $json.string.test18 }}"
},
{
"name": "numberToBoolean1",
"type": "booleanValue",
"booleanValue": "={{ $json.number.test3 }}"
},
{
"name": "numberToBoolean2",
"type": "booleanValue",
"booleanValue": "={{ $json.number.test7 }}"
}
]
},
"include": "none",
"options": {}
},
"id": "a1b841e9-c0fc-4c6a-9090-29fb317d506a",
"name": "To Boolean",
"type": "n8n-nodes-base.set",
"typeVersion": 3,
"position": [1000, 1460]
},
{
"parameters": {
"fields": {
"values": [
{
"name": "stringToArray1",
"type": "arrayValue",
"arrayValue": "={{ $json.string.test11 }}"
},
{
"name": "stringToArray2",
"type": "arrayValue",
"arrayValue": "={{ $json.string.test13 }}"
},
{
"name": "arrayToArray1",
"type": "arrayValue",
"arrayValue": "={{ $json.array.test13 }}"
}
]
},
"include": "none",
"options": {}
},
"id": "7b9d4152-3ded-446d-a5e8-6bf562f077ab",
"name": "To Array",
"type": "n8n-nodes-base.set",
"typeVersion": 3,
"position": [1000, 1660]
},
{
"parameters": {
"fields": {
"values": [
{
"name": "stringToObject1",
"type": "objectValue",
"objectValue": "={{ $json.string.test14 }}"
},
{
"name": "stringToObject2",
"type": "objectValue",
"objectValue": "={{ $json.string.test15 }}"
}
]
},
"include": "none",
"options": {}
},
"id": "f50a1fce-acd8-439e-86f6-ddc39d21490d",
"name": "To Object",
"type": "n8n-nodes-base.set",
"typeVersion": 3,
"position": [1000, 1840]
},
{
"parameters": {
"content": "### Strict type checking",
"height": 1063.125,
"width": 369.6875
},
"id": "29b19a78-3d26-4582-b7b3-4194b8891e0f",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [880, 1020]
},
{
"parameters": {
"content": "### Loose type checking",
"height": 1058.046875,
"width": 310.703125
},
"id": "54a01a58-9d93-49d6-8964-a2a4e6b6e341",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [1340, 1020]
},
{
"parameters": {
"fields": {
"values": [
{
"name": "stringToNumber1",
"type": "numberValue",
"numberValue": "={{ $json.string.test2 }}"
},
{
"name": "stringToNumber2",
"type": "numberValue",
"numberValue": "={{ $json.string.test3 }}"
},
{
"name": "stringToNumber3",
"type": "numberValue",
"numberValue": "={{ $json.string.test9 }}"
},
{
"name": "arrayToNumber1",
"type": "numberValue",
"numberValue": "={{ $json.array.test13 }}"
}
]
},
"include": "none",
"options": {
"ignoreConversionErrors": true
}
},
"id": "e28c0f50-7309-428c-99a8-8bacdc6e11a4",
"name": "To Number1",
"type": "n8n-nodes-base.set",
"typeVersion": 3,
"position": [1440, 1080]
},
{
"parameters": {
"fields": {
"values": [
{
"name": "stringToBoolean1",
"type": "booleanValue",
"booleanValue": "={{ $json.string.test5 }}"
},
{
"name": "stringToBoolean2",
"type": "booleanValue",
"booleanValue": "=3,14"
},
{
"name": "stringToBoolean3",
"type": "booleanValue",
"booleanValue": "={{ $json.string.test7 }}"
},
{
"name": "stringToBoolean4",
"type": "booleanValue",
"booleanValue": "={{ $json.string.test11 }}"
},
{
"name": "stringToBoolean5",
"type": "booleanValue",
"booleanValue": "={{ $json.string.test12 }}"
},
{
"name": "stringToBoolean6",
"type": "booleanValue",
"booleanValue": "={{ $json.string.test17 }}"
},
{
"name": "numberToBoolean1",
"type": "booleanValue",
"booleanValue": "={{ $json.number.test1 }}"
},
{
"name": "numberToBoolean2",
"type": "booleanValue",
"booleanValue": "={{ $json.number.test4 }}"
}
]
},
"include": "none",
"options": {
"ignoreConversionErrors": true
}
},
"id": "ee602441-15de-4b9f-91b7-b96e3ca6eb64",
"name": "To Boolean1",
"type": "n8n-nodes-base.set",
"typeVersion": 3,
"position": [1440, 1280]
},
{
"parameters": {
"fields": {
"values": [
{
"name": "stringToArray1",
"type": "arrayValue",
"arrayValue": "={{ $json.string.test2 }}"
},
{
"name": "stringToArray2",
"type": "arrayValue",
"arrayValue": "={{ $json.string.test5 }}"
}
]
},
"include": "none",
"options": {
"ignoreConversionErrors": true
}
},
"id": "d2aa68a0-5a23-4f75-9fae-35777cb19ae3",
"name": "To Array1",
"type": "n8n-nodes-base.set",
"typeVersion": 3,
"position": [1440, 1460]
},
{
"parameters": {
"fields": {
"values": [
{
"name": "stringToObject1",
"type": "objectValue",
"objectValue": "={{ $json.string.test14 }}"
},
{
"name": "stringToObject2",
"type": "objectValue",
"objectValue": "={{ $json.string.test15 }}"
}
]
},
"include": "none",
"options": {
"ignoreConversionErrors": true
}
},
"id": "98250306-4ec5-4ddc-abe4-3a6558ab26fd",
"name": "To Object1",
"type": "n8n-nodes-base.set",
"typeVersion": 3,
"position": [1440, 1660]
},
{
"parameters": {},
"id": "7c79bfc1-02ca-43b8-b75f-0c2afcc80187",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [940, 680]
},
{
"parameters": {},
"id": "fdf64186-6911-4f0e-8ebd-534eb3b5e28f",
"name": "No Operation, do nothing1",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1200, 680]
},
{
"parameters": {
"content": "### Keep Input Fields",
"height": 752.046875,
"width": 310.703125
},
"id": "c51416d4-e560-4337-ab56-efbf90ead55b",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [700, 2120]
},
{
"parameters": {
"fields": {
"values": [
{
"name": "test",
"stringValue": "foo"
}
]
},
"options": {}
},
"id": "b1e0a125-adc5-4048-b34f-309f8c752ef4",
"name": "Keep All",
"type": "n8n-nodes-base.set",
"typeVersion": 3,
"position": [800, 2180]
},
{
"parameters": {
"fields": {
"values": [
{
"name": "test",
"stringValue": "foo"
}
]
},
"include": "selected",
"includeFields": "name,object",
"options": {}
},
"id": "b47927f2-170f-4b07-8e1d-d29959263772",
"name": "Keep Selected",
"type": "n8n-nodes-base.set",
"typeVersion": 3,
"position": [800, 2340]
},
{
"parameters": {
"fields": {
"values": [
{
"name": "test",
"stringValue": "foo"
}
]
},
"include": "except",
"excludeFields": "age,name",
"options": {}
},
"id": "741bd22b-9572-43f9-ada1-0c03d06ec2c7",
"name": "Keep All Except",
"type": "n8n-nodes-base.set",
"typeVersion": 3,
"position": [800, 2500]
},
{
"parameters": {
"data": [
{
"name": "John Doe",
"age": 32,
"object": {
"nested": true
}
},
{
"name": "Jane Doe",
"age": 35,
"object": {
"nested": true
}
}
]
},
"id": "c120da29-6807-4059-9a2a-13b9032f93cd",
"name": "Code",
"type": "n8n-nodes-testing.testData",
"typeVersion": 1,
"position": [480, 2420]
}
],
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Code1",
"type": "main",
"index": 0
},
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Code1": {
"main": [
[
{
"node": "To String",
"type": "main",
"index": 0
},
{
"node": "To Number",
"type": "main",
"index": 0
},
{
"node": "To Boolean",
"type": "main",
"index": 0
},
{
"node": "To Array",
"type": "main",
"index": 0
},
{
"node": "To Object",
"type": "main",
"index": 0
},
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
},
"No Operation, do nothing": {
"main": [
[
{
"node": "No Operation, do nothing1",
"type": "main",
"index": 0
}
]
]
},
"No Operation, do nothing1": {
"main": [
[
{
"node": "To Number1",
"type": "main",
"index": 0
},
{
"node": "To Boolean1",
"type": "main",
"index": 0
},
{
"node": "To Array1",
"type": "main",
"index": 0
},
{
"node": "To Object1",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "Keep All",
"type": "main",
"index": 0
},
{
"node": "Keep Selected",
"type": "main",
"index": 0
},
{
"node": "Keep All Except",
"type": "main",
"index": 0
},
{
"node": "Keep None",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {
"Keep None": [
{
"json": { "test": "foo" }
},
{
"json": { "test": "foo" }
}
],
"To String": [
{
"json": {
"numberToString1": "52472",
"numberToString2": "-1",
"numberToString3": "1.334535",
"boolToString1": "true",
"boolToString2": "false",
"arrayToString1": "[1,2,3,4]",
"objectToString1": "{\"objKey\":2,\"objArray\":[1,2,3,4],\"objBool\":true}"
}
}
],
"To Number": [
{
"json": {
"stringToNumber1": 3,
"stringToNumber2": 3.14,
"boolToNumber1": 1,
"boolToNumber2": 0
}
}
],
"To Boolean": [
{
"json": {
"stringToBoolean1": true,
"stringToBoolean3": false,
"stringToBoolean4": true,
"stringToBoolean5": false,
"numberToBoolean1": false,
"numberToBoolean2": true
}
}
],
"To Array": [
{
"json": {
"stringToArray1": ["apples", "oranges"],
"stringToArray2": [1, 2],
"arrayToArray1": [1, 2, 3, 4]
}
}
],
"To Object": [
{
"json": {
"stringToObject1": {
"a": 1,
"b": {
"c": 10,
"d": "test"
}
},
"stringToObject2": {
"a": 1
}
}
}
],
"To Number1": [
{
"json": {
"stringToNumber1": "hello",
"stringToNumber2": 0,
"stringToNumber3": "false",
"arrayToNumber1": [1, 2, 3, 4]
}
}
],
"To Boolean1": [
{
"json": {
"stringToBoolean1": "3",
"stringToBoolean2": "3,14",
"stringToBoolean3": "3.14",
"stringToBoolean4": "[\"apples\", \"oranges\"]",
"stringToBoolean5": "\"apples\", \"oranges\"",
"stringToBoolean6": "undefined",
"numberToBoolean1": 52472,
"numberToBoolean2": 1.334535
}
}
],
"To Array1": [
{
"json": {
"stringToArray1": "hello",
"stringToArray2": "3"
}
}
],
"To Object1": [
{
"json": {
"stringToObject1": {
"a": 1,
"b": {
"c": 10,
"d": "test"
}
},
"stringToObject2": {
"a": 1
}
}
}
],
"Keep All": [
{
"json": {
"name": "John Doe",
"age": 32,
"object": {
"nested": true
},
"test": "foo"
}
},
{
"json": {
"name": "Jane Doe",
"age": 35,
"object": {
"nested": true
},
"test": "foo"
}
}
],
"Keep Selected": [
{
"json": {
"name": "John Doe",
"object": {
"nested": true
},
"test": "foo"
}
},
{
"json": {
"name": "Jane Doe",
"object": {
"nested": true
},
"test": "foo"
}
}
],
"Keep All Except": [
{
"json": {
"object": {
"nested": true
},
"test": "foo"
}
},
{
"json": {
"object": {
"nested": true
},
"test": "foo"
}
}
]
}
}
@@ -0,0 +1,874 @@
{
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "27cc9b56542ad45b38725555722c50a1c3fee1670bbb67980558314ee08517c4"
},
"nodes": [
{
"parameters": {},
"id": "487e9636-56d5-4955-9193-17dee520bdd0",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [160, 4240]
},
{
"parameters": {
"data": [
{
"string": {
"test2": "hello",
"test3": " ",
"test4": "",
"test5": "3",
"test6": "3,14",
"test7": "3.14",
"test8": "TRUE",
"test9": "false",
"test10": "1",
"test11": "[\"apples\", \"oranges\"]",
"test12": "\"apples\", \"oranges\"",
"test13": "[1, 2]",
"test14": "{\"a\": 1, \"b\": { \"c\": 10, \"d\": \"test\"}}",
"test15": "{\"a\": 1}",
"test16": "null",
"test17": "undefined",
"test18": "0"
},
"number": {
"test1": 52472,
"test2": -1,
"test3": 0,
"test4": 1.334535,
"test5": null,
"test7": 1
},
"boolean": {
"test3": true,
"test4": false
},
"date": {
"test1": "2024-01-01T00:00:00.000Z",
"test2": "2023-08-01T12:34:56Z",
"test3": "2016-05-25T09:24:15.123",
"test4": "Tue, 01 Nov 2016 13:23:12 +0630",
"test5": "2017-05-15 09:24:15",
"test6": "1542674993",
"test7": 1542674993
},
"array": {
"test13": [1, 2, 3, 4]
},
"object": {
"obj": {
"objKey": 2,
"objArray": [1, 2, 3, 4],
"objBool": true
}
}
}
]
},
"id": "8afc51b7-f9ed-417a-a58b-2fe251ba4b93",
"name": "Code1",
"type": "n8n-nodes-testing.testData",
"typeVersion": 1,
"position": [420, 4240]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "1",
"name": "numberToString1",
"type": "string",
"value": "={{ $json.number.test1 }}"
},
{
"id": "2",
"name": "numberToString2",
"type": "string",
"value": "={{ $json.number.test2 }}"
},
{
"id": "3",
"name": "numberToString3",
"type": "string",
"value": "={{ $json.number.test4 }}"
},
{
"id": "1",
"name": "boolToString1",
"type": "string",
"value": "={{ $json.boolean.test3 }}"
},
{
"id": "4",
"name": "boolToString2",
"type": "string",
"value": "={{ $json.boolean.test4 }}"
},
{
"id": "5",
"name": "arrayToString1",
"type": "string",
"value": "={{ $json.array.test13 }}"
},
{
"id": "6",
"name": "objectToString1",
"type": "string",
"value": "={{ $json.object.obj }}"
}
]
},
"options": {}
},
"id": "8ede737c-5648-4c8e-82b7-fa5193d8ff92",
"name": "To String",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [780, 3860]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "1",
"name": "stringToNumber1",
"type": "number",
"value": "={{ $json.string.test5 }}"
},
{
"id": "2",
"name": "stringToNumber2",
"type": "number",
"value": "={{ $json.string.test7 }}"
},
{
"id": "3",
"name": "boolToNumber1",
"type": "number",
"value": "={{ $json.boolean.test3 }}"
},
{
"id": "4",
"name": "boolToNumber2",
"type": "number",
"value": "={{ $json.boolean.test4 }}"
}
]
},
"options": {}
},
"id": "064976c4-b13d-4e2e-9a07-bab165b0efd7",
"name": "To Number",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [780, 4060]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "1",
"name": "stringToBoolean1",
"type": "boolean",
"value": "={{ $json.string.test8 }}"
},
{
"id": "2",
"name": "stringToBoolean3",
"type": "boolean",
"value": "={{ $json.string.test9 }}"
},
{
"id": "3",
"name": "stringToBoolean4",
"type": "boolean",
"value": "={{ $json.string.test10 }}"
},
{
"id": "4",
"name": "stringToBoolean5",
"type": "boolean",
"value": "={{ $json.string.test18 }}"
},
{
"id": "5",
"name": "numberToBoolean1",
"type": "boolean",
"value": "={{ $json.number.test3 }}"
},
{
"id": "6",
"name": "numberToBoolean2",
"type": "boolean",
"value": "={{ $json.number.test7 }}"
}
]
},
"options": {}
},
"id": "dfcdddde-cf89-44c5-94e7-f11e1f7e1a58",
"name": "To Boolean",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [780, 4240]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "1",
"name": "stringToArray1",
"type": "array",
"value": "={{ $json.string.test11 }}"
},
{
"id": "2",
"name": "stringToArray2",
"type": "array",
"value": "={{ $json.string.test13 }}"
},
{
"id": "3",
"name": "arrayToArray1",
"type": "array",
"value": "={{ $json.array.test13 }}"
}
]
},
"options": {}
},
"id": "7ea149df-c231-40fc-a95c-c062860896e6",
"name": "To Array",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [780, 4440]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "1",
"name": "stringToObject1",
"type": "object",
"value": "={{ $json.string.test14 }}"
},
{
"id": "2",
"name": "stringToObject2",
"type": "object",
"value": "={{ $json.string.test15 }}"
}
]
},
"options": {}
},
"id": "c775efa3-2508-466d-93b9-51d4c5ca9eca",
"name": "To Object",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [780, 4620]
},
{
"parameters": {
"content": "### Strict type checking",
"height": 1063.125,
"width": 369.6875
},
"id": "d646183a-b6ea-48e5-99cc-a4be39959558",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [700, 3800]
},
{
"parameters": {
"content": "### Loose type checking",
"height": 1058.046875,
"width": 310.703125
},
"id": "cb382bde-1cba-4019-a45a-b50fbeb2f8ac",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [1180, 3804.375]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "1",
"name": "stringToNumber1",
"type": "number",
"value": "={{ $json.string.test2 }}"
},
{
"id": "2",
"name": "stringToNumber2",
"type": "number",
"value": "={{ $json.string.test3 }}"
},
{
"id": "3",
"name": "stringToNumber3",
"type": "number",
"value": "={{ $json.string.test9 }}"
},
{
"id": "4",
"name": "arrayToNumber1",
"type": "number",
"value": "={{ $json.array.test13 }}"
}
]
},
"options": {
"ignoreConversionErrors": true
}
},
"id": "d9c91c3d-de1e-430e-acb2-c832d1b41b21",
"name": "To Number1",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [1220, 3860]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "1",
"name": "stringToBoolean1",
"type": "boolean",
"value": "={{ $json.string.test5 }}"
},
{
"id": "2",
"name": "stringToBoolean2",
"type": "boolean",
"value": "=3,14"
},
{
"id": "3",
"name": "stringToBoolean3",
"type": "boolean",
"value": "={{ $json.string.test7 }}"
},
{
"id": "4",
"name": "stringToBoolean4",
"type": "boolean",
"value": "={{ $json.string.test11 }}"
},
{
"id": "5",
"name": "stringToBoolean5",
"type": "boolean",
"value": "={{ $json.string.test12 }}"
},
{
"id": "6",
"name": "stringToBoolean6",
"type": "boolean",
"value": "={{ $json.string.test17 }}"
},
{
"id": "7",
"name": "numberToBoolean1",
"type": "boolean",
"value": "={{ $json.number.test1 }}"
},
{
"id": "8",
"name": "numberToBoolean2",
"type": "boolean",
"value": "={{ $json.number.test4 }}"
}
]
},
"options": {
"ignoreConversionErrors": true
}
},
"id": "2a62319a-9850-4aed-ba83-f7e1fae233d6",
"name": "To Boolean1",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [1220, 4060]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "1",
"name": "stringToArray1",
"type": "array",
"value": "={{ $json.string.test2 }}"
},
{
"id": "2",
"name": "stringToArray2",
"type": "array",
"value": "={{ $json.string.test5 }}"
}
]
},
"options": {
"ignoreConversionErrors": true
}
},
"id": "72275d21-9a12-4030-98b9-4a8ef37e0158",
"name": "To Array1",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [1220, 4240]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "1",
"name": "stringToObject1",
"type": "object",
"value": "={{ $json.string.test14 }}"
},
{
"id": "2",
"name": "stringToObject2",
"type": "object",
"value": "={{ $json.string.test15 }}"
}
]
},
"options": {
"ignoreConversionErrors": true
}
},
"id": "c6b117f1-44b9-41ef-ad26-b8c77e37ed9e",
"name": "To Object1",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [1220, 4440]
},
{
"parameters": {},
"id": "8e3ce059-9c1a-4490-b5a8-6cfb22e43632",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [720, 3460]
},
{
"parameters": {},
"id": "e581a88a-900e-4d9a-9dcd-6a4b6e30c39a",
"name": "No Operation, do nothing1",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [980, 3460]
},
{
"parameters": {
"content": "### Keep Input Fields",
"height": 752.046875,
"width": 310.703125
},
"id": "e03d8297-9eed-4679-9a8a-fc3e004ec0c7",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [700, 4940]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "c42efef4-240d-42b8-baf7-f9edc10a25ba",
"name": "test",
"value": "foo",
"type": "string"
}
]
},
"includeOtherFields": true,
"options": {}
},
"id": "cf0799c0-cf07-43f9-89ce-0d218f13f23f",
"name": "Keep All",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [800, 5000]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "ae431a73-282b-4547-b82a-f491ca244ad2",
"name": "test",
"value": "foo",
"type": "string"
}
]
},
"includeOtherFields": true,
"include": "selected",
"includeFields": "object, age",
"options": {}
},
"id": "90575fc8-502c-4a22-ba27-bcf13a73e71a",
"name": "Keep Selected",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [800, 5160]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "a1d28431-fd8e-4360-baec-f390515d57fd",
"name": "test",
"value": "foo",
"type": "string"
}
]
},
"includeOtherFields": true,
"include": "except",
"excludeFields": "age",
"options": {}
},
"id": "731be3d3-b08f-4e13-9f67-badae737d581",
"name": "Keep All Except",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [800, 5320]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "23c2d8eb-7326-43ae-80b0-e651611f5f43",
"name": "test",
"value": "foo",
"type": "string"
}
]
},
"options": {}
},
"id": "f310db17-beb9-436c-99ec-e0fa1deea94d",
"name": "Keep None",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [800, 5500]
},
{
"parameters": {
"data": [
{
"name": "John Doe",
"age": 32,
"object": {
"nested": true
}
},
{
"name": "Jane Doe",
"age": 35,
"object": {
"nested": true
}
}
]
},
"id": "7f9a81c4-5207-493f-bf48-9e52e4c350c6",
"name": "Code",
"type": "n8n-nodes-testing.testData",
"typeVersion": 1,
"position": [480, 5240]
}
],
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Code1",
"type": "main",
"index": 0
},
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Code1": {
"main": [
[
{
"node": "To String",
"type": "main",
"index": 0
},
{
"node": "To Number",
"type": "main",
"index": 0
},
{
"node": "To Boolean",
"type": "main",
"index": 0
},
{
"node": "To Array",
"type": "main",
"index": 0
},
{
"node": "To Object",
"type": "main",
"index": 0
},
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
},
"No Operation, do nothing": {
"main": [
[
{
"node": "No Operation, do nothing1",
"type": "main",
"index": 0
}
]
]
},
"No Operation, do nothing1": {
"main": [
[
{
"node": "To Number1",
"type": "main",
"index": 0
},
{
"node": "To Boolean1",
"type": "main",
"index": 0
},
{
"node": "To Array1",
"type": "main",
"index": 0
},
{
"node": "To Object1",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "Keep All",
"type": "main",
"index": 0
},
{
"node": "Keep Selected",
"type": "main",
"index": 0
},
{
"node": "Keep All Except",
"type": "main",
"index": 0
},
{
"node": "Keep None",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {
"To String": [
{
"json": {
"numberToString1": "52472",
"numberToString2": "-1",
"numberToString3": "1.334535",
"boolToString1": "true",
"boolToString2": "false",
"arrayToString1": "[1,2,3,4]",
"objectToString1": "{\"objKey\":2,\"objArray\":[1,2,3,4],\"objBool\":true}"
}
}
],
"To Number": [
{
"json": {
"stringToNumber1": 3,
"stringToNumber2": 3.14,
"boolToNumber1": 1,
"boolToNumber2": 0
}
}
],
"To Boolean": [
{
"json": {
"stringToBoolean1": true,
"stringToBoolean3": false,
"stringToBoolean4": true,
"stringToBoolean5": false,
"numberToBoolean1": false,
"numberToBoolean2": true
}
}
],
"To Array": [
{
"json": {
"stringToArray1": ["apples", "oranges"],
"stringToArray2": [1, 2],
"arrayToArray1": [1, 2, 3, 4]
}
}
],
"To Object": [
{
"json": {
"stringToObject1": {
"a": 1,
"b": {
"c": 10,
"d": "test"
}
},
"stringToObject2": {
"a": 1
}
}
}
],
"To Number1": [
{
"json": {
"stringToNumber1": "hello",
"stringToNumber2": 0,
"stringToNumber3": "false",
"arrayToNumber1": [1, 2, 3, 4]
}
}
],
"To Boolean1": [
{
"json": {
"stringToBoolean1": "3",
"stringToBoolean2": "3,14",
"stringToBoolean3": "3.14",
"stringToBoolean4": "[\"apples\", \"oranges\"]",
"stringToBoolean5": "\"apples\", \"oranges\"",
"stringToBoolean6": "undefined",
"numberToBoolean1": 52472,
"numberToBoolean2": 1.334535
}
}
],
"To Array1": [
{
"json": {
"stringToArray1": "hello",
"stringToArray2": "3"
}
}
],
"To Object1": [
{
"json": {
"stringToObject1": {
"a": 1,
"b": {
"c": 10,
"d": "test"
}
},
"stringToObject2": {
"a": 1
}
}
}
],
"Keep All": [
{
"json": {
"name": "John Doe",
"age": 32,
"object": {
"nested": true
},
"test": "foo"
}
},
{
"json": {
"name": "Jane Doe",
"age": 35,
"object": {
"nested": true
},
"test": "foo"
}
}
],
"Keep Selected": [
{
"json": {
"object": {
"nested": true
},
"age": 32,
"test": "foo"
}
},
{
"json": {
"object": {
"nested": true
},
"age": 35,
"test": "foo"
}
}
]
}
}
@@ -0,0 +1,295 @@
{
"name": "Set Node Test",
"nodes": [
{
"parameters": {},
"id": "90ddfdf0-973b-4b4f-b082-0e013b70629a",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [900, 2560]
},
{
"parameters": {
"values": {
"string": [
{
"name": "string",
"value": "foo"
}
],
"boolean": [
{
"name": "boolean",
"value": true
}
],
"number": [
{
"name": "number",
"value": 1
}
]
},
"options": {}
},
"id": "b46b7da8-3c0f-4111-bb3f-973fd2827dc4",
"name": "Set",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": [1380, 2200]
},
{
"parameters": {
"data": [
{
"nested": {
"foo": "bar"
},
"list": ["foo"]
}
]
},
"id": "a7e51ac7-734a-4652-bb02-d9f0a4d8410b",
"name": "Code",
"type": "n8n-nodes-testing.testData",
"typeVersion": 1,
"position": [1120, 2560]
},
{
"parameters": {
"keepOnlySet": true,
"values": {
"string": [
{
"name": "string",
"value": "foo"
}
],
"boolean": [
{
"name": "boolean",
"value": true
}
],
"number": [
{
"name": "number",
"value": 1
}
]
},
"options": {}
},
"id": "3744306e-7a23-486f-881c-accf3a81e08a",
"name": "Set Keep Only Set",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": [1380, 2380]
},
{
"parameters": {
"values": {
"string": [
{
"name": "list[1]",
"value": "bar"
}
],
"boolean": [
{
"name": "list[2]",
"value": true
}
],
"number": [
{
"name": "list[3]",
"value": 1
}
]
},
"options": {}
},
"id": "60ab031b-f5e2-4aa7-9427-6379f3a75550",
"name": "Set to Array",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": [1380, 2940]
},
{
"parameters": {
"values": {
"string": [
{
"name": "nested.string",
"value": "foo"
}
],
"boolean": [
{
"name": "nested.boolean",
"value": true
}
],
"number": [
{
"name": "nested.number",
"value": 1
}
]
},
"options": {}
},
"id": "24e0d35a-f2d4-4121-8cf1-7eb9ab2ed79e",
"name": "Set to Object",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": [1380, 2560]
},
{
"parameters": {
"values": {
"string": [
{
"name": "nested.string",
"value": "foo"
}
],
"boolean": [
{
"name": "nested.boolean",
"value": true
}
],
"number": [
{
"name": "nested.number",
"value": 1
}
]
},
"options": {
"dotNotation": false
}
},
"id": "e9c34a76-28ac-455a-8f77-1d6118b01291",
"name": "Set Disable Dot Notation",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": [1380, 2740]
}
],
"pinData": {
"Set": [
{
"json": {
"nested": {
"foo": "bar"
},
"list": ["foo"],
"boolean": true,
"number": 1,
"string": "foo"
}
}
],
"Set Keep Only Set": [
{
"json": {
"boolean": true,
"number": 1,
"string": "foo"
}
}
],
"Set to Object": [
{
"json": {
"nested": {
"foo": "bar",
"boolean": true,
"number": 1,
"string": "foo"
},
"list": ["foo"]
}
}
],
"Set Disable Dot Notation": [
{
"json": {
"nested": {
"foo": "bar"
},
"list": ["foo"],
"nested.boolean": true,
"nested.number": 1,
"nested.string": "foo"
}
}
],
"Set to Array": [
{
"json": {
"nested": {
"foo": "bar"
},
"list": ["foo", "bar", true, 1]
}
}
]
},
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "Set",
"type": "main",
"index": 0
},
{
"node": "Set Keep Only Set",
"type": "main",
"index": 0
},
{
"node": "Set to Object",
"type": "main",
"index": 0
},
{
"node": "Set to Array",
"type": "main",
"index": 0
},
{
"node": "Set Disable Dot Notation",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {},
"versionId": "de6493c7-31ec-40b4-ad2e-44bca10515c0",
"id": "183",
"meta": {
"instanceId": "104a4d08d8897b8bdeb38aaca515021075e0bd8544c983c2bb8c86e6a8e6081c"
},
"tags": []
}
@@ -0,0 +1,244 @@
{
"name": "My workflow 31",
"nodes": [
{
"parameters": {},
"id": "9fe8b524-b618-4abb-821f-227ae5cc1f12",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
560,
640
]
},
{
"parameters": {
"fields": {
"values": [
{
"name": "test1",
"stringValue": "={{ null }}"
},
{
"name": "test2",
"stringValue": "={{ undefined }}"
}
]
},
"options": {
"ignoreConversionErrors": true
}
},
"id": "78f2ad0d-e129-448b-ae78-5bfdba587186",
"name": "Edit Fields",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [
1000,
240
]
},
{
"parameters": {
"fields": {
"values": [
{
"name": "test1",
"type": "numberValue",
"numberValue": "={{ null }}"
},
{
"name": "test2",
"type": "numberValue",
"numberValue": "={{ undefined }}"
}
]
},
"options": {
"ignoreConversionErrors": true
}
},
"id": "ceaad5a1-b78c-4d38-b80d-f608f8c95f71",
"name": "Edit Fields1",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [
1000,
440
]
},
{
"parameters": {
"fields": {
"values": [
{
"name": "test1",
"type": "booleanValue",
"booleanValue": "={{ null }}"
},
{
"name": "test2",
"type": "booleanValue",
"booleanValue": "={{ undefined }}"
}
]
},
"options": {
"ignoreConversionErrors": true
}
},
"id": "0048be99-0bce-4073-86e6-2622169fe9ab",
"name": "Edit Fields2",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [
1000,
640
]
},
{
"parameters": {
"fields": {
"values": [
{
"name": "test1",
"type": "arrayValue",
"arrayValue": "={{ null }}"
},
{
"name": "test2",
"type": "arrayValue",
"arrayValue": "={{ undefined }}"
}
]
},
"options": {
"ignoreConversionErrors": true
}
},
"id": "aa254a34-9267-42a2-9730-c2ab64760721",
"name": "Edit Fields3",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [
1000,
840
]
},
{
"parameters": {
"fields": {
"values": [
{
"name": "test1",
"type": "objectValue",
"objectValue": "={{ null }}"
},
{
"name": "test2",
"type": "objectValue",
"objectValue": "={{ undefined }}"
}
]
},
"options": {
"ignoreConversionErrors": true
}
},
"id": "03e4acf2-ceaf-4a27-adb7-d074a95e9cc9",
"name": "Edit Fields4",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [
1000,
1020
]
}
],
"pinData": {
"Edit Fields": [
{
"json": {
"test1": null,
"test2": null
}
}
],
"Edit Fields1": [
{
"json": {
"test1": null,
"test2": null
}
}
],
"Edit Fields2": [
{
"json": {
"test1": null,
"test2": null
}
}
],
"Edit Fields3": [
{
"json": {
"test1": null,
"test2": null
}
}
],
"Edit Fields4": [
{
"json": {
"test1": null,
"test2": null
}
}
]
},
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
},
{
"node": "Edit Fields1",
"type": "main",
"index": 0
},
{
"node": "Edit Fields2",
"type": "main",
"index": 0
},
{
"node": "Edit Fields3",
"type": "main",
"index": 0
},
{
"node": "Edit Fields4",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "016f70db-c529-43e9-a077-2e0d81859747",
"id": "ZfMuVQ1861mlPIKe",
"meta": {
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
},
"tags": []
}
@@ -0,0 +1,145 @@
{
"name": "My workflow 31",
"nodes": [
{
"parameters": {},
"id": "5363525f-2065-4132-82cc-808941e51d47",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [-380, -40]
},
{
"parameters": {
"values": {
"string": [
{
"name": "myNumber",
"value": "123456"
}
]
},
"options": {}
},
"id": "9e6fa15e-f6d8-4764-815b-832ff00c531c",
"name": "Set",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": [-80, -40]
},
{
"parameters": {
"keepOnlySet": true,
"values": {
"number": [
{
"name": "updatedNumber",
"value": "={{ $json[\"myNumber\"] }}"
}
]
},
"options": {}
},
"id": "4893a819-9f21-446a-b89f-3079d87d5f40",
"name": "Set1",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": [140, -40]
},
{
"parameters": {
"keepOnlySet": true,
"values": {
"number": [
{
"name": "numField",
"value": "={{ $json.updatedNumber }}"
}
]
},
"options": {}
},
"id": "8e6b36ca-4f50-4a51-b42f-8098d71248bf",
"name": "Set2",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [540, -40]
},
{
"parameters": {
"content": "## Set Node V1 sets numbers as string",
"height": 265,
"width": 479
},
"id": "12610911-6841-486e-924a-af66cececdca",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [-146, -120]
},
{
"parameters": {
"content": "## Set Node V2 sets numbers as numbers",
"height": 264,
"width": 480
},
"id": "e5896250-3e0f-47ac-b47f-9f24bac4f52f",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [420, -120]
}
],
"pinData": {
"Set2": [
{
"json": {
"numField": 123456
}
}
]
},
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Set",
"type": "main",
"index": 0
}
]
]
},
"Set": {
"main": [
[
{
"node": "Set1",
"type": "main",
"index": 0
}
]
]
},
"Set1": {
"main": [
[
{
"node": "Set2",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {},
"versionId": "f15acb8d-6e15-4962-b568-66f5a412efe0",
"id": "240",
"meta": {
"instanceId": "104a4d08d8897b8bdeb38aaca515021075e0bd8544c983c2bb8c86e6a8e6081c"
},
"tags": []
}
@@ -0,0 +1,133 @@
import get from 'lodash/get';
import { constructExecutionMetaData } from 'n8n-core';
import {
NodeOperationError,
type IDataObject,
type IExecuteFunctions,
type IGetNodeParameterOptions,
type INode,
} from 'n8n-workflow';
import { type SetNodeOptions } from '../../v2/helpers/interfaces';
import { execute } from '../../v2/manual.mode';
const node: INode = {
id: '11',
name: 'Set Node',
type: 'n8n-nodes-base.set',
typeVersion: 3.4,
position: [42, 42],
parameters: {
mode: 'manual',
fields: {
values: [],
},
include: 'none',
options: {},
},
};
const createMockExecuteFunction = (
nodeParameters: IDataObject,
continueOnFail: boolean = false,
) => {
const fakeExecuteFunction = {
getNodeParameter(
parameterName: string,
_itemIndex: number,
fallbackValue?: IDataObject,
options?: IGetNodeParameterOptions,
) {
const parameter = options?.extractValue ? `${parameterName}.value` : parameterName;
return get(nodeParameters, parameter, fallbackValue);
},
getNode() {
return node;
},
getWorkflowSettings() {
return {};
},
helpers: { constructExecutionMetaData },
continueOnFail: () => continueOnFail,
} as unknown as IExecuteFunctions;
return fakeExecuteFunction;
};
describe('test Set2, manual Mode', () => {
const item = {
json: {
input1: 'value1',
input2: 2,
input3: [1, 2, 3],
},
pairedItem: {
item: 0,
input: undefined,
},
};
const options: SetNodeOptions = {
include: 'none',
};
const rawData = {
num1: 55,
str1: '42',
arr1: ['foo', 'bar'],
obj: {
key: 'value',
},
};
afterEach(() => jest.resetAllMocks());
describe('fixed mode', () => {
const assignments = {
assignments: [
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
name: 'test',
value: '{ ok: 1 }',
type: 'object',
},
],
};
const fakeExecuteFunction = createMockExecuteFunction({ assignments });
it('should parse json with the jsonOutput in node parameter and compose a return item', async () => {
const result = await execute.call(fakeExecuteFunction, item, 0, options, rawData, node);
expect(result).toEqual({ json: { test: { ok: 1 } }, pairedItem: { item: 0 } });
});
});
describe('error handling', () => {
const assignments = {
assignments: [
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
name: 'test',
value: '{ ok: 1',
type: 'object',
},
],
};
it('should return an error object with pairedItem when continueOnFail is true', async () => {
const fakeExecuteFunction = createMockExecuteFunction({ assignments }, true);
const output = await execute.call(fakeExecuteFunction, item, 0, options, {}, node);
expect(output).toEqual({
json: { error: "'test' expects a object but we got '{ ok: 1' [item 0]" },
pairedItem: { item: 0 },
});
});
it('should throw an error when continueOnFail is false', async () => {
const fakeExecuteFunction = createMockExecuteFunction({ assignments }, false);
await expect(execute.call(fakeExecuteFunction, item, 0, options, {}, node)).rejects.toThrow(
NodeOperationError,
);
});
});
});
@@ -0,0 +1,145 @@
import get from 'lodash/get';
import { constructExecutionMetaData } from 'n8n-core';
import {
NodeOperationError,
type IDataObject,
type IExecuteFunctions,
type IGetNodeParameterOptions,
type INode,
} from 'n8n-workflow';
import { type SetNodeOptions } from '../../v2/helpers/interfaces';
import * as utils from '../../v2/helpers/utils';
import { execute } from '../../v2/raw.mode';
const node: INode = {
id: '11',
name: 'Set Node',
type: 'n8n-nodes-base.set',
typeVersion: 3,
position: [42, 42],
parameters: {
mode: 'raw',
fields: {
values: [],
},
include: 'none',
options: {},
},
};
const createMockExecuteFunction = (
nodeParameters: IDataObject,
continueOnFail: boolean = false,
) => {
const fakeExecuteFunction = {
getNodeParameter(
parameterName: string,
_itemIndex: number,
fallbackValue?: IDataObject,
options?: IGetNodeParameterOptions,
) {
const parameter = options?.extractValue ? `${parameterName}.value` : parameterName;
return get(nodeParameters, parameter, fallbackValue);
},
getNode() {
return node;
},
helpers: { constructExecutionMetaData },
continueOnFail: () => continueOnFail,
} as unknown as IExecuteFunctions;
return fakeExecuteFunction;
};
describe('test Set2, rawMode/json Mode', () => {
const item = {
json: {
input1: 'value1',
input2: 2,
input3: [1, 2, 3],
},
pairedItem: {
item: 0,
input: undefined,
},
};
const options: SetNodeOptions = {
include: 'none',
};
afterEach(() => jest.resetAllMocks());
describe('fixed mode', () => {
const jsonData = { jsonData: 1 };
const fakeExecuteFunction = createMockExecuteFunction({ jsonOutput: jsonData });
const rawData = {
num1: 55,
str1: '42',
arr1: ['foo', 'bar'],
obj: {
key: 'value',
},
};
it('should parse json with the jsonOutput in node parameter and compose a return item', async () => {
jest.spyOn(utils, 'parseJsonParameter');
jest.spyOn(utils, 'composeReturnItem');
const result = await execute.call(fakeExecuteFunction, item, 0, options, rawData, node);
expect(result).toEqual({ json: jsonData, pairedItem: { item: 0 } });
expect(utils.parseJsonParameter).toHaveBeenCalledWith(jsonData, node, 0);
expect(utils.composeReturnItem).toHaveBeenCalledWith(0, item, jsonData, options, 3);
});
});
describe('expression mode', () => {
const jsonData = { my_field_1: 'value' };
const jsonDataString = '{"my_field_1": "value"}';
const fakeExecuteFunction = createMockExecuteFunction({ jsonOutput: jsonDataString });
const rawData = {
num1: 55,
str1: '42',
arr1: ['foo', 'bar'],
obj: {
key: 'value',
},
jsonOutput: jsonDataString,
};
it('should parse json with resolved expression data and compose a return item', async () => {
jest.spyOn(utils, 'parseJsonParameter');
jest.spyOn(utils, 'composeReturnItem');
jest.spyOn(utils, 'resolveRawData');
const result = await execute.call(fakeExecuteFunction, item, 0, options, rawData, node);
expect(utils.parseJsonParameter).toHaveBeenCalledWith(jsonDataString, node, 0);
expect(utils.composeReturnItem).toHaveBeenCalledWith(0, item, jsonData, options, 3);
expect(utils.resolveRawData).toHaveBeenCalledWith(jsonDataString, 0);
expect(result).toEqual({ json: jsonData, pairedItem: { item: 0 } });
});
});
describe('error handling', () => {
it('should return an error object with pairedItem when continueOnFail is true', async () => {
const fakeExecuteFunction = createMockExecuteFunction({ jsonOutput: 'jsonData' }, true);
const output = await execute.call(fakeExecuteFunction, item, 0, options, {}, node);
expect(output).toEqual({
json: { error: "The 'JSON Output' in item 0 does not contain a valid JSON object" },
pairedItem: { item: 0 },
});
});
it('should throw an error when continueOnFail is false', async () => {
const fakeExecuteFunction = createMockExecuteFunction({ jsonOutput: 'jsonData' }, false);
await expect(execute.call(fakeExecuteFunction, item, 0, options, {}, node)).rejects.toThrow(
NodeOperationError,
);
});
});
});
@@ -0,0 +1,435 @@
import get from 'lodash/get';
import { constructExecutionMetaData } from 'n8n-core';
import type { IDataObject, IExecuteFunctions, IGetNodeParameterOptions, INode } from 'n8n-workflow';
import type { SetNodeOptions } from '../../v2/helpers/interfaces';
import {
composeReturnItem,
parseJsonParameter,
validateEntry,
resolveRawData,
} from '../../v2/helpers/utils';
export const node: INode = {
id: '11',
name: 'Edit Fields',
type: 'n8n-nodes-base.set',
typeVersion: 3,
position: [42, 42],
parameters: {
mode: 'manual',
fields: {
values: [],
},
include: 'none',
options: {},
},
};
export const createMockExecuteFunction = (nodeParameters: IDataObject) => {
const fakeExecuteFunction = {
getNodeParameter(
parameterName: string,
_itemIndex: number,
fallbackValue?: IDataObject,
options?: IGetNodeParameterOptions,
) {
const parameter = options?.extractValue ? `${parameterName}.value` : parameterName;
return get(nodeParameters, parameter, fallbackValue);
},
getNode() {
return node;
},
helpers: { constructExecutionMetaData },
continueOnFail: () => false,
} as unknown as IExecuteFunctions;
return fakeExecuteFunction;
};
describe('test Set2, composeReturnItem', () => {
it('should compose return item including no other fields', () => {
const fakeExecuteFunction = createMockExecuteFunction({});
const inputItem = {
json: {
input1: 'value1',
input2: 2,
input3: [1, 2, 3],
},
pairedItem: {
item: 0,
input: undefined,
},
};
const newData = {
num1: 55,
str1: '42',
arr1: ['foo', 'bar'],
obj: {
key: 'value',
},
};
const options: SetNodeOptions = {
include: 'none',
};
const result = composeReturnItem.call(fakeExecuteFunction, 0, inputItem, newData, options, 3.4);
expect(result).toEqual({
json: {
num1: 55,
str1: '42',
arr1: ['foo', 'bar'],
obj: {
key: 'value',
},
},
pairedItem: {
item: 0,
},
});
});
it('should compose return item including selected fields', () => {
const fakeExecuteFunction = createMockExecuteFunction({ includeFields: 'input1, input2' });
const inputItem = {
json: {
input1: 'value1',
input2: 2,
input3: [1, 2, 3],
},
pairedItem: {
item: 0,
input: undefined,
},
};
const newData = {
num1: 55,
str1: '42',
arr1: ['foo', 'bar'],
obj: {
key: 'value',
},
};
const options: SetNodeOptions = {
include: 'selected',
};
const result = composeReturnItem.call(fakeExecuteFunction, 0, inputItem, newData, options, 3.4);
expect(result).toEqual({
json: {
num1: 55,
str1: '42',
arr1: ['foo', 'bar'],
input1: 'value1',
input2: 2,
obj: {
key: 'value',
},
},
pairedItem: {
item: 0,
},
});
});
it('should include binary when expected in version <3.4', () => {
const fakeExecuteFunction = createMockExecuteFunction({});
const inputItem = {
json: {
input1: 'value1',
input2: 2,
input3: [1, 2, 3],
},
pairedItem: {
item: 0,
input: undefined,
},
binary: {
data: {
data: 'content',
mimeType: 'image/jpg',
},
},
};
const newData = {
num1: 55,
str1: '42',
arr1: ['foo', 'bar'],
obj: {
key: 'value',
},
};
const resultWithIncludeBinary = composeReturnItem.call(
fakeExecuteFunction,
0,
inputItem,
newData,
{
include: 'all',
includeBinary: true,
},
3.3,
);
expect(resultWithIncludeBinary.binary).toEqual(inputItem.binary);
const resultWithoutIncludeBinary = composeReturnItem.call(
fakeExecuteFunction,
0,
inputItem,
newData,
{
include: 'all',
},
3.3,
);
expect(resultWithoutIncludeBinary.binary).toBeUndefined();
});
it('should include binary when expected in version >=3.4', () => {
const fakeExecuteFunction = createMockExecuteFunction({});
const inputItem = {
json: {
input1: 'value1',
input2: 2,
input3: [1, 2, 3],
},
pairedItem: {
item: 0,
input: undefined,
},
binary: {
data: {
data: 'content',
mimeType: 'image/jpg',
},
},
};
const newData = {
num1: 55,
str1: '42',
arr1: ['foo', 'bar'],
obj: {
key: 'value',
},
};
const resultWithStripBinary = composeReturnItem.call(
fakeExecuteFunction,
0,
inputItem,
newData,
{
include: 'all',
stripBinary: true,
},
3.4,
);
expect(resultWithStripBinary.binary).toBeUndefined();
const resultWithoutStripBinary = composeReturnItem.call(
fakeExecuteFunction,
0,
inputItem,
newData,
{
include: 'all',
},
3.4,
);
expect(resultWithoutStripBinary.binary).toEqual(inputItem.binary);
});
});
describe('test Set2, parseJsonParameter', () => {
it('should parse valid JSON string', () => {
const result = parseJsonParameter('{"foo": "bar"}', node, 0, 'test');
expect(result).toEqual({
foo: 'bar',
});
});
it('should tolerate single quotes in string', () => {
const result = parseJsonParameter("{'foo': 'bar'}", node, 0, 'test');
expect(result).toEqual({
foo: 'bar',
});
});
it('should tolerate unquoted keys', () => {
const result = parseJsonParameter("{foo: 'bar'}", node, 0, 'test');
expect(result).toEqual({
foo: 'bar',
});
});
it('should tolerate trailing comma', () => {
const result = parseJsonParameter('{"foo": "bar"},', node, 0, 'test');
expect(result).toEqual({
foo: 'bar',
});
});
it('should tolerate trailing commas in objects', () => {
const result = parseJsonParameter("{foo: 'bar', baz: {'foo': 'bar',}, }", node, 0, 'test');
expect(result).toEqual({
foo: 'bar',
baz: {
foo: 'bar',
},
});
});
});
describe('test Set2, validateEntry', () => {
it('should convert number to string', () => {
const result = validateEntry('foo', 'string', 42 as unknown as string, node, 0);
expect(result).toEqual({
name: 'foo',
value: '42',
});
});
it('should convert array to string', () => {
const result = validateEntry('foo', 'string', [1, 2, 3] as unknown as string, node, 0);
expect(result).toEqual({
name: 'foo',
value: '[1,2,3]',
});
});
it('should convert object to string', () => {
const result = validateEntry('foo', 'string', { foo: 'bar' } as unknown as string, node, 0);
expect(result).toEqual({
name: 'foo',
value: '{"foo":"bar"}',
});
});
it('should convert boolean to string', () => {
const result = validateEntry('foo', 'string', true as unknown as string, node, 0);
expect(result).toEqual({
name: 'foo',
value: 'true',
});
});
it('should convert undefined to string', () => {
const result = validateEntry('foo', 'string', undefined as unknown as string, node, 0);
expect(result).toEqual({
name: 'foo',
value: 'undefined',
});
});
});
describe('test Set2, resolveRawData', () => {
const createMockExecuteFunctionForResolve = (returnValue: any) => {
return {
evaluateExpression: jest.fn().mockReturnValue(returnValue),
} as unknown as IExecuteFunctions;
};
it('should handle strings with special replacement patterns like $&', () => {
const mockFunction = createMockExecuteFunctionForResolve('hello world $&');
const input = '{{ "hello world $&" }}';
const result = resolveRawData.call(mockFunction, input, 0);
// The result should contain the literal $& and not have it replaced
expect(result).toBe('hello world $&');
expect(mockFunction.evaluateExpression).toHaveBeenCalledWith('{{ "hello world $&" }}', 0);
});
it('should handle strings with $` pattern', () => {
const mockFunction = createMockExecuteFunctionForResolve('hello world $`');
const input = '{{ "hello world $`" }}';
const result = resolveRawData.call(mockFunction, input, 0);
expect(result).toBe('hello world $`');
});
it("should handle strings with $' pattern", () => {
const mockFunction = createMockExecuteFunctionForResolve("hello world $'");
const input = '{{ "hello world $\'" }}';
const result = resolveRawData.call(mockFunction, input, 0);
expect(result).toBe("hello world $'");
});
it('should handle strings with $n pattern', () => {
const mockFunction = createMockExecuteFunctionForResolve('hello world $1 $2');
const input = '{{ "hello world $1 $2" }}';
const result = resolveRawData.call(mockFunction, input, 0);
expect(result).toBe('hello world $1 $2');
});
it('should handle JSON objects with special patterns', () => {
const mockFunction = createMockExecuteFunctionForResolve({ message: 'hello world $&' });
const input = '{{ {"message": "hello world $&"} }}';
const result = resolveRawData.call(mockFunction, input, 0);
expect(result).toBe('{"message":"hello world $&"}');
});
it('should handle multiple expressions with special patterns', () => {
const mockFunction = {
evaluateExpression: jest.fn().mockReturnValueOnce('hello $&').mockReturnValueOnce('world $`'),
} as unknown as IExecuteFunctions;
const input = 'start {{ expr1 }} middle {{ expr2 }} end';
const result = resolveRawData.call(mockFunction, input, 0);
expect(result).toBe('start hello $& middle world $` end');
});
it('should handle regular strings without special patterns', () => {
const mockFunction = createMockExecuteFunctionForResolve('hello world');
const input = '{{ "hello world" }}';
const result = resolveRawData.call(mockFunction, input, 0);
expect(result).toBe('hello world');
});
it('should handle objects and stringify them', () => {
const mockFunction = createMockExecuteFunctionForResolve({ message: 'hello world', count: 42 });
const input = '{{ someExpression }}';
const result = resolveRawData.call(mockFunction, input, 0);
expect(result).toBe('{"message":"hello world","count":42}');
});
it('should return raw data when no resolvables are found', () => {
const mockFunction = createMockExecuteFunctionForResolve('unused');
const input = 'hello world without expressions';
const result = resolveRawData.call(mockFunction, input, 0);
expect(result).toBe('hello world without expressions');
expect(mockFunction.evaluateExpression).not.toHaveBeenCalled();
});
});
@@ -0,0 +1,225 @@
import set from 'lodash/set';
import type {
IExecuteFunctions,
INodeExecutionData,
INodeParameters,
INodeType,
INodeTypeBaseDescription,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeConnectionTypes, deepCopy } from 'n8n-workflow';
const versionDescription: INodeTypeDescription = {
displayName: 'Set',
name: 'set',
icon: 'fa:pen',
group: ['input'],
version: [1, 2],
description: 'Sets values on items and optionally remove other values',
defaults: {
name: 'Set',
color: '#0000FF',
},
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
properties: [
{
displayName: 'Keep Only Set',
name: 'keepOnlySet',
type: 'boolean',
default: false,
description: 'Whether only the values set on this node should be kept and all others removed',
},
{
displayName: 'Values to Set',
name: 'values',
placeholder: 'Add Value',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
sortable: true,
},
description: 'The value to set',
default: {},
options: [
{
name: 'boolean',
displayName: 'Boolean',
values: [
{
displayName: 'Name',
name: 'name',
type: 'string',
requiresDataPath: 'single',
default: 'propertyName',
description:
'Name of the property to write data to. Supports dot-notation. Example: "data.person[0].name"',
},
{
displayName: 'Value',
name: 'value',
type: 'boolean',
default: false,
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
description: 'The boolean value to write in the property',
},
],
},
{
name: 'number',
displayName: 'Number',
values: [
{
displayName: 'Name',
name: 'name',
type: 'string',
default: 'propertyName',
requiresDataPath: 'single',
description:
'Name of the property to write data to. Supports dot-notation. Example: "data.person[0].name"',
},
{
displayName: 'Value',
name: 'value',
type: 'number',
default: 0,
description: 'The number value to write in the property',
},
],
},
{
name: 'string',
displayName: 'String',
values: [
{
displayName: 'Name',
name: 'name',
type: 'string',
default: 'propertyName',
requiresDataPath: 'single',
description:
'Name of the property to write data to. Supports dot-notation. Example: "data.person[0].name"',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'The string value to write in the property',
},
],
},
],
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add option',
default: {},
options: [
{
displayName: 'Dot Notation',
name: 'dotNotation',
type: 'boolean',
default: true,
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
description:
'<p>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} }.<p></p>If that is not intended this can be deactivated, it will then set { "a.b": value } instead.</p>.',
},
],
},
],
};
export class SetV1 implements INodeType {
description: INodeTypeDescription;
constructor(baseDescription: INodeTypeBaseDescription) {
this.description = {
...baseDescription,
...versionDescription,
};
}
async execute(this: IExecuteFunctions) {
const items = this.getInputData();
const nodeVersion = this.getNode().typeVersion;
if (items.length === 0) {
items.push({ json: {}, pairedItem: { item: 0 } });
}
const returnData: INodeExecutionData[] = [];
let item: INodeExecutionData;
let keepOnlySet: boolean;
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
keepOnlySet = this.getNodeParameter('keepOnlySet', itemIndex, false) as boolean;
item = items[itemIndex];
const options = this.getNodeParameter('options', itemIndex, {});
const newItem: INodeExecutionData = {
json: {},
pairedItem: { item: itemIndex },
};
if (!keepOnlySet) {
if (item.binary !== undefined) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
newItem.binary = {};
Object.assign(newItem.binary, item.binary);
}
newItem.json = deepCopy(item.json);
}
// Add boolean values
(this.getNodeParameter('values.boolean', itemIndex, []) as INodeParameters[]).forEach(
(setItem) => {
if (options.dotNotation === false) {
newItem.json[setItem.name as string] = !!setItem.value;
} else {
set(newItem.json, setItem.name as string, !!setItem.value);
}
},
);
// Add number values
(this.getNodeParameter('values.number', itemIndex, []) as INodeParameters[]).forEach(
(setItem) => {
if (
nodeVersion >= 2 &&
typeof setItem.value === 'string' &&
!Number.isNaN(Number(setItem.value))
) {
setItem.value = Number(setItem.value);
}
if (options.dotNotation === false) {
newItem.json[setItem.name as string] = setItem.value;
} else {
set(newItem.json, setItem.name as string, setItem.value);
}
},
);
// Add string values
(this.getNodeParameter('values.string', itemIndex, []) as INodeParameters[]).forEach(
(setItem) => {
if (options.dotNotation === false) {
newItem.json[setItem.name as string] = setItem.value;
} else {
set(newItem.json, setItem.name as string, setItem.value);
}
},
);
returnData.push(newItem);
}
return [returnData];
}
}
@@ -0,0 +1,373 @@
import type {
IDataObject,
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeBaseDescription,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
import type { IncludeMods, SetField, SetNodeOptions } from './helpers/interfaces';
import { INCLUDE } from './helpers/interfaces';
import * as manual from './manual.mode';
import * as raw from './raw.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: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.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,
description: 'Whether this item should be duplicated a set number of times',
},
{
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'],
'@version': [3, 3.1, 3.2],
},
},
},
{
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'],
'@version': [3, 3.1, 3.2],
},
},
},
{
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'],
'/includeOtherFields': [true],
},
hide: {
'@version': [3, 3.1, 3.2],
},
},
},
{
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'],
'/includeOtherFields': [true],
},
hide: {
'@version': [3, 3.1, 3.2],
},
},
},
{
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];
}
}
@@ -0,0 +1,806 @@
/* eslint-disable n8n-nodes-base/node-param-display-name-miscased */
import { mock } from 'jest-mock-extended';
import type {
AssignmentCollectionValue,
IBinaryData,
IExecuteFunctions,
INode,
INodeExecutionData,
} from 'n8n-workflow';
import { NodeOperationError, BINARY_MODE_COMBINED } from 'n8n-workflow';
import type { SetNodeOptions } from '../interfaces';
import { INCLUDE } from '../interfaces';
import { parseJsonParameter, prepareReturnItem } from '../utils';
describe('prepareReturnItem', () => {
const mockNode = mock<INode>({
name: 'Set Node',
typeVersion: 3.4,
});
const mockContext = mock<IExecuteFunctions>({
getNodeParameter: jest.fn(),
getWorkflowSettings: jest.fn(() => ({})),
helpers: {
assertBinaryData: jest.fn(),
},
});
const baseItem: INodeExecutionData = {
json: { existingField: 'existingValue' },
binary: {
existingBinary: {
data: 'base64data',
mimeType: 'text/plain',
fileName: 'existing.txt',
},
},
};
const baseOptions: SetNodeOptions = {
dotNotation: true,
ignoreConversionErrors: false,
include: INCLUDE.ALL,
stripBinary: false,
};
beforeEach(() => {
jest.clearAllMocks();
(mockContext.getNodeParameter as jest.Mock).mockReturnValue('');
(mockContext.getWorkflowSettings as jest.Mock).mockReturnValue({});
});
describe('JSON assignments', () => {
it('should handle string assignments', () => {
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'stringField',
value: 'test string',
type: 'string',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.json).toEqual({
existingField: 'existingValue',
stringField: 'test string',
});
});
it('should handle number assignments', () => {
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'numberField',
value: 42,
type: 'number',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.json).toEqual({
existingField: 'existingValue',
numberField: 42,
});
});
it('should handle boolean assignments', () => {
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'booleanField',
value: true,
type: 'boolean',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.json).toEqual({
existingField: 'existingValue',
booleanField: true,
});
});
it('should handle array assignments', () => {
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'arrayField',
value: '["item1", "item2"]',
type: 'array',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.json).toHaveProperty('arrayField');
});
it('should handle object assignments', () => {
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'objectField',
value: '{"nested": "value"}',
type: 'object',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.json).toHaveProperty('objectField');
});
it('should handle multiple JSON assignments', () => {
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'field1',
value: 'value1',
type: 'string',
},
{
id: '2',
name: 'field2',
value: 123,
type: 'number',
},
{
id: '3',
name: 'field3',
value: false,
type: 'boolean',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.json).toEqual({
existingField: 'existingValue',
field1: 'value1',
field2: 123,
field3: false,
});
});
});
describe('binary assignments - mode handling', () => {
describe('non-combined binary mode', () => {
it('should push binary assignments to binaryValues when binaryMode is not combined', () => {
const mockBinaryData: IBinaryData = {
data: 'binarydata',
mimeType: 'image/png',
fileName: 'image.png',
};
(mockContext.helpers.assertBinaryData as jest.Mock).mockReturnValue(mockBinaryData);
(mockContext.getWorkflowSettings as jest.Mock).mockReturnValue({ binaryMode: 'default' });
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'binaryField',
value: 'binaryProp',
type: 'binary',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.binary).toHaveProperty('binaryField');
expect(result.binary?.binaryField).toEqual(mockBinaryData);
});
it('should handle binary assignments in separate mode', () => {
const mockBinaryData: IBinaryData = {
data: 'separatedata',
mimeType: 'application/pdf',
fileName: 'doc.pdf',
};
(mockContext.helpers.assertBinaryData as jest.Mock).mockReturnValue(mockBinaryData);
(mockContext.getWorkflowSettings as jest.Mock).mockReturnValue({ binaryMode: 'separate' });
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'pdfBinary',
value: 'pdfProp',
type: 'binary',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.binary).toHaveProperty('pdfBinary');
expect(result.binary?.pdfBinary).toEqual(mockBinaryData);
});
});
describe('combined binary mode', () => {
it('should place binary only in binary when id is missing', () => {
const mockBinaryData: IBinaryData = {
data: 'combineddata',
mimeType: 'text/plain',
fileName: 'file.txt',
};
(mockContext.helpers.assertBinaryData as jest.Mock).mockReturnValue(mockBinaryData);
(mockContext.getWorkflowSettings as jest.Mock).mockReturnValue({
binaryMode: BINARY_MODE_COMBINED,
});
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'textBinary',
value: 'binaryProp',
type: 'binary',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.binary).toHaveProperty('textBinary');
expect(result.binary?.textBinary).toEqual(mockBinaryData);
expect(result.json).not.toHaveProperty('textBinary');
});
it('should place binary in json when id is present', () => {
const mockBinaryDataWithId: IBinaryData = {
data: 'combineddata',
mimeType: 'image/jpeg',
fileName: 'photo.jpg',
id: 'binary-id-123',
};
(mockContext.helpers.assertBinaryData as jest.Mock).mockReturnValue(mockBinaryDataWithId);
(mockContext.getWorkflowSettings as jest.Mock).mockReturnValue({
binaryMode: BINARY_MODE_COMBINED,
});
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'photoField',
value: 'binaryProp',
type: 'binary',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.json).toHaveProperty('photoField');
expect(result.json.photoField).toEqual(mockBinaryDataWithId);
});
it('should handle mixed binary assignments (with and without id)', () => {
const mockBinaryDataNoId: IBinaryData = {
data: 'data1',
mimeType: 'text/plain',
fileName: 'file1.txt',
};
const mockBinaryDataWithId: IBinaryData = {
data: 'data2',
mimeType: 'text/plain',
fileName: 'file2.txt',
id: 'binary-id-456',
};
(mockContext.helpers.assertBinaryData as jest.Mock)
.mockReturnValueOnce(mockBinaryDataNoId)
.mockReturnValueOnce(mockBinaryDataWithId);
(mockContext.getWorkflowSettings as jest.Mock).mockReturnValue({
binaryMode: BINARY_MODE_COMBINED,
});
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'binaryNoId',
value: 'binaryProp1',
type: 'binary',
},
{
id: '2',
name: 'binaryWithId',
value: 'binaryProp2',
type: 'binary',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.binary).toHaveProperty('binaryNoId');
expect(result.binary?.binaryNoId).toEqual(mockBinaryDataNoId);
expect(result.json).not.toHaveProperty('binaryNoId');
expect(result.json).toHaveProperty('binaryWithId');
expect(result.json.binaryWithId).toEqual(mockBinaryDataWithId);
});
it('should treat empty string id as missing id (place only in binary)', () => {
const mockBinaryDataEmptyId: IBinaryData = {
id: '',
data: 'emptyiddata',
mimeType: 'text/plain',
fileName: 'file.txt',
};
(mockContext.helpers.assertBinaryData as jest.Mock).mockReturnValue(mockBinaryDataEmptyId);
(mockContext.getWorkflowSettings as jest.Mock).mockReturnValue({
binaryMode: BINARY_MODE_COMBINED,
});
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'emptyIdBinary',
value: 'binaryProp',
type: 'binary',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.binary).toHaveProperty('emptyIdBinary');
expect(result.binary?.emptyIdBinary).toEqual(mockBinaryDataEmptyId);
expect(result.json).not.toHaveProperty('emptyIdBinary');
});
it('should handle string reference in non-combined mode and resolve via assertBinaryData', () => {
const mockBinaryData: IBinaryData = {
data: 'referenceddata',
mimeType: 'application/json',
fileName: 'data.json',
};
(mockContext.helpers.assertBinaryData as jest.Mock).mockReturnValue(mockBinaryData);
(mockContext.getWorkflowSettings as jest.Mock).mockReturnValue({ binaryMode: 'default' });
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'binaryRef',
value: 'binaryPropertyName',
type: 'binary',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(mockContext.helpers.assertBinaryData).toHaveBeenCalledWith(0, 'binaryPropertyName');
expect(result.binary).toHaveProperty('binaryRef');
expect(result.binary?.binaryRef).toEqual(mockBinaryData);
});
});
describe('mixed mode scenarios', () => {
it('should handle JSON and binary assignments together in non-combined mode', () => {
const mockBinaryData: IBinaryData = {
data: 'mixeddata',
mimeType: 'image/png',
fileName: 'image.png',
};
(mockContext.helpers.assertBinaryData as jest.Mock).mockReturnValue(mockBinaryData);
(mockContext.getWorkflowSettings as jest.Mock).mockReturnValue({ binaryMode: 'default' });
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'stringField',
value: 'text value',
type: 'string',
},
{
id: '2',
name: 'binaryField',
value: 'binaryProp',
type: 'binary',
},
{
id: '3',
name: 'numberField',
value: 42,
type: 'number',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.json).toHaveProperty('stringField', 'text value');
expect(result.json).toHaveProperty('numberField', 42);
expect(result.binary).toHaveProperty('binaryField');
});
it('should handle JSON and binary assignments together in combined mode with id', () => {
const mockBinaryDataWithId: IBinaryData = {
data: 'combineddata',
mimeType: 'video/mp4',
fileName: 'video.mp4',
id: 'video-id-789',
};
(mockContext.helpers.assertBinaryData as jest.Mock).mockReturnValue(mockBinaryDataWithId);
(mockContext.getWorkflowSettings as jest.Mock).mockReturnValue({
binaryMode: BINARY_MODE_COMBINED,
});
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'title',
value: 'My Video',
type: 'string',
},
{
id: '2',
name: 'videoData',
value: 'binaryProp',
type: 'binary',
},
{
id: '3',
name: 'duration',
value: 120,
type: 'number',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.json).toHaveProperty('title', 'My Video');
expect(result.json).toHaveProperty('duration', 120);
expect(result.json).toHaveProperty('videoData', mockBinaryDataWithId);
});
});
});
describe('binary assignments', () => {
it('should handle binary assignments', () => {
const mockBinaryData: IBinaryData = {
data: 'newbinarydata',
mimeType: 'application/pdf',
fileName: 'document.pdf',
};
(mockContext.helpers.assertBinaryData as jest.Mock).mockReturnValue(mockBinaryData);
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'newBinary',
value: 'binaryPropertyName',
type: 'binary',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.binary).toEqual({
existingBinary: {
data: 'base64data',
mimeType: 'text/plain',
fileName: 'existing.txt',
},
newBinary: mockBinaryData,
});
expect(mockContext.helpers.assertBinaryData).toHaveBeenCalledWith(0, 'binaryPropertyName');
});
it('should handle multiple binary assignments', () => {
const mockBinaryData1: IBinaryData = {
data: 'binary1',
mimeType: 'image/png',
fileName: 'image1.png',
};
const mockBinaryData2: IBinaryData = {
data: 'binary2',
mimeType: 'image/jpeg',
fileName: 'image2.jpg',
};
(mockContext.helpers.assertBinaryData as jest.Mock)
.mockReturnValueOnce(mockBinaryData1)
.mockReturnValueOnce(mockBinaryData2);
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'image1',
value: 'binaryProp1',
type: 'binary',
},
{
id: '2',
name: 'image2',
value: 'binaryProp2',
type: 'binary',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.binary).toEqual({
existingBinary: {
data: 'base64data',
mimeType: 'text/plain',
fileName: 'existing.txt',
},
image1: mockBinaryData1,
image2: mockBinaryData2,
});
});
it('should create binary object when item has no existing binary data', () => {
const itemWithoutBinary: INodeExecutionData = {
json: { field: 'value' },
};
const mockBinaryData: IBinaryData = {
data: 'binarydata',
mimeType: 'text/plain',
fileName: 'test.txt',
};
(mockContext.helpers.assertBinaryData as jest.Mock).mockReturnValue(mockBinaryData);
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'newBinary',
value: 'binaryPropertyName',
type: 'binary',
},
],
};
const result = prepareReturnItem(
mockContext,
value,
0,
itemWithoutBinary,
mockNode,
baseOptions,
);
expect(result.binary).toEqual({
newBinary: mockBinaryData,
});
});
it('should throw error when binary data is not found', () => {
(mockContext.helpers.assertBinaryData as jest.Mock).mockReturnValue('not-binary-data');
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'invalidBinary',
value: 'nonExistentBinary',
type: 'binary',
},
],
};
expect(() =>
prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions),
).toThrow(NodeOperationError);
expect(() =>
prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions),
).toThrow('Could not find binary data specified in field invalidBinary');
});
});
describe('Mixed assignments', () => {
it('should handle both JSON and binary assignments', () => {
const mockBinaryData: IBinaryData = {
data: 'mixedbinary',
mimeType: 'application/json',
fileName: 'data.json',
};
(mockContext.helpers.assertBinaryData as jest.Mock).mockReturnValue(mockBinaryData);
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'textField',
value: 'mixed content',
type: 'string',
},
{
id: '2',
name: 'numberField',
value: 999,
type: 'number',
},
{
id: '3',
name: 'binaryField',
value: 'binaryProp',
type: 'binary',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.json).toEqual({
existingField: 'existingValue',
textField: 'mixed content',
numberField: 999,
});
expect(result.binary).toEqual({
existingBinary: {
data: 'base64data',
mimeType: 'text/plain',
fileName: 'existing.txt',
},
binaryField: mockBinaryData,
});
});
});
describe('Edge cases', () => {
it('should handle empty assignments array', () => {
const value: AssignmentCollectionValue = {
assignments: [],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.json).toEqual({
existingField: 'existingValue',
});
expect(result.binary).toEqual({
existingBinary: {
data: 'base64data',
mimeType: 'text/plain',
fileName: 'existing.txt',
},
});
});
it('should handle undefined assignments', () => {
const value: AssignmentCollectionValue = {
assignments: undefined as any,
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.json).toEqual({
existingField: 'existingValue',
});
});
it('should handle null assignments value', () => {
const result = prepareReturnItem(
mockContext,
null as any,
0,
baseItem,
mockNode,
baseOptions,
);
expect(result.json).toEqual({
existingField: 'existingValue',
});
});
it('should handle assignments with null/undefined values', () => {
const value: AssignmentCollectionValue = {
assignments: [
{
id: '1',
name: 'nullField',
value: null,
type: 'string',
},
{
id: '2',
name: 'undefinedField',
value: undefined,
type: 'string',
},
],
};
const result = prepareReturnItem(mockContext, value, 0, baseItem, mockNode, baseOptions);
expect(result.json).toEqual({
existingField: 'existingValue',
nullField: null,
undefinedField: null,
});
});
});
});
describe('parseJsonParameter', () => {
const mockNode = mock<INode>({
name: 'Set Node',
typeVersion: 3.4,
});
describe('Valid JSON input', () => {
it('should parse valid JSON string', () => {
const result = parseJsonParameter('{"key": "value"}', mockNode, 0);
expect(result).toEqual({ key: 'value' });
});
it('should parse valid JSON object directly', () => {
const input = { key: 'value' };
const result = parseJsonParameter(input, mockNode, 0);
expect(result).toEqual(input);
});
it('should parse complex valid JSON', () => {
const json = '{"nested": {"key": "value"}, "array": [1, 2, 3]}';
const result = parseJsonParameter(json, mockNode, 0);
expect(result).toEqual({ nested: { key: 'value' }, array: [1, 2, 3] });
});
});
describe('JSON repair', () => {
it('should repair JSON string', () => {
const result = parseJsonParameter('{key: "value"}', mockNode, 0);
expect(result).toEqual({ key: 'value' });
});
});
describe('Error cases', () => {
it('should throw error for invalid JSON that cannot be recovered', () => {
expect(() => parseJsonParameter('{key1: "value",, key2: "value2"}', mockNode, 0)).toThrow(
"The 'JSON Output' in item 0 contains invalid JSON",
);
});
});
});
@@ -0,0 +1,34 @@
import type { IDataObject } from 'n8n-workflow';
export type SetNodeOptions = {
dotNotation?: boolean;
ignoreConversionErrors?: boolean;
include?: IncludeMods;
includeBinary?: boolean;
stripBinary?: boolean;
};
export type SetField = {
name: string;
type: 'stringValue' | 'numberValue' | 'booleanValue' | 'arrayValue' | 'objectValue';
stringValue?: string;
numberValue?: number;
booleanValue?: boolean;
arrayValue?: string[] | string | IDataObject | IDataObject[];
objectValue?: string | IDataObject;
};
export type AssignmentSetField = {
name: string;
value: unknown;
type: string;
};
export const INCLUDE = {
ALL: 'all',
NONE: 'none',
SELECTED: 'selected',
EXCEPT: 'except',
} as const;
export type IncludeMods = (typeof INCLUDE)[keyof typeof INCLUDE];
@@ -0,0 +1,318 @@
import get from 'lodash/get';
import set from 'lodash/set';
import unset from 'lodash/unset';
import {
ApplicationError,
NodeOperationError,
deepCopy,
getValueDescription,
jsonParse,
validateFieldType,
isBinaryValue,
BINARY_MODE_COMBINED,
} from 'n8n-workflow';
import type {
AssignmentCollectionValue,
FieldType,
IDataObject,
IExecuteFunctions,
INode,
INodeExecutionData,
ISupplyDataFunctions,
} from 'n8n-workflow';
import type { SetNodeOptions } from './interfaces';
import { INCLUDE } from './interfaces';
import { getResolvables, sanitizeDataPathKey } from '../../../../utils/utilities';
const configureFieldHelper = (dotNotation?: boolean) => {
if (dotNotation !== false) {
return {
set: (item: IDataObject, key: string, value: IDataObject) => {
set(item, key, value);
},
get: (item: IDataObject, key: string) => {
return get(item, key);
},
unset: (item: IDataObject, key: string) => {
unset(item, key);
},
};
} else {
return {
set: (item: IDataObject, key: string, value: IDataObject) => {
item[sanitizeDataPathKey(item, key)] = value;
},
get: (item: IDataObject, key: string) => {
return item[sanitizeDataPathKey(item, key)];
},
unset: (item: IDataObject, key: string) => {
delete item[sanitizeDataPathKey(item, key)];
},
};
}
};
export function composeReturnItem(
this: IExecuteFunctions | ISupplyDataFunctions,
itemIndex: number,
inputItem: INodeExecutionData,
newFields: IDataObject,
options: SetNodeOptions,
nodeVersion: number,
) {
const newItem: INodeExecutionData = {
json: {},
pairedItem: { item: itemIndex },
};
const includeBinary =
(nodeVersion >= 3.4 && !options.stripBinary && options.include !== 'none') ||
(nodeVersion < 3.4 && !!options.includeBinary);
if (includeBinary && inputItem.binary !== undefined) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
newItem.binary = {};
Object.assign(newItem.binary, inputItem.binary);
}
const fieldHelper = configureFieldHelper(options.dotNotation);
switch (options.include) {
case INCLUDE.ALL:
newItem.json = deepCopy(inputItem.json);
break;
case INCLUDE.SELECTED:
const includeFields = (this.getNodeParameter('includeFields', itemIndex) as string)
.split(',')
.map((item) => item.trim())
.filter((item) => item);
for (const key of includeFields) {
const fieldValue = fieldHelper.get(inputItem.json, key) as IDataObject;
let keyToSet = key;
if (options.dotNotation !== false && key.includes('.')) {
keyToSet = key.split('.').pop() as string;
}
fieldHelper.set(newItem.json, keyToSet, fieldValue);
}
break;
case INCLUDE.EXCEPT:
const excludeFields = (this.getNodeParameter('excludeFields', itemIndex) as string)
.split(',')
.map((item) => item.trim())
.filter((item) => item);
const inputData = deepCopy(inputItem.json);
for (const key of excludeFields) {
fieldHelper.unset(inputData, key);
}
newItem.json = inputData;
break;
case INCLUDE.NONE:
break;
default:
throw new ApplicationError(`The include option "${options.include}" is not known!`, {
level: 'warning',
});
}
for (const key of Object.keys(newFields)) {
fieldHelper.set(newItem.json, key, newFields[key] as IDataObject);
}
return newItem;
}
export const parseJsonParameter = (
jsonData: string | IDataObject,
node: INode,
i: number,
entryName?: string,
) => {
let returnData: IDataObject;
const location = entryName ? `entry "${entryName}" inside 'Fields to Set'` : "'JSON Output'";
if (typeof jsonData === 'string') {
try {
returnData = jsonParse<IDataObject>(jsonData, { repairJSON: true });
} catch (error) {
throw new NodeOperationError(node, `The ${location} in item ${i} contains invalid JSON`, {
description: jsonData,
});
}
} else {
returnData = jsonData;
}
if (returnData === undefined || typeof returnData !== 'object' || Array.isArray(returnData)) {
throw new NodeOperationError(
node,
`The ${location} in item ${i} does not contain a valid JSON object`,
);
}
return returnData;
};
export const validateEntry = (
name: string,
type: FieldType,
value: unknown,
node: INode,
itemIndex: number,
ignoreErrors = false,
nodeVersion?: number,
) => {
if (nodeVersion && nodeVersion >= 3.2 && (value === undefined || value === null)) {
return { name, value: null };
}
const description = `To fix the error try to change the type for the field "${name}" or activate the option “Ignore Type Conversion Errors” to apply a less strict type validation`;
if (type === 'string') {
if (nodeVersion && nodeVersion > 3 && (value === undefined || value === null)) {
if (ignoreErrors) {
return { name, value: null };
} else {
throw new NodeOperationError(
node,
`'${name}' expects a ${type} but we got ${getValueDescription(value)} [item ${itemIndex}]`,
{ description },
);
}
} else if (typeof value === 'object') {
value = JSON.stringify(value);
} else {
value = String(value);
}
}
const validationResult = validateFieldType(name, value, type);
if (!validationResult.valid) {
if (ignoreErrors) {
return { name, value: value ?? null };
} else {
const message = `${'errorMessage' in validationResult ? validationResult.errorMessage : 'Error'} [item ${itemIndex}]`;
throw new NodeOperationError(node, message, {
itemIndex,
description,
});
}
}
return {
name,
value: validationResult.newValue ?? null,
};
};
export function resolveRawData(
this: IExecuteFunctions | ISupplyDataFunctions,
rawData: string,
i: number,
) {
const resolvables = getResolvables(rawData);
let returnData: string = rawData;
if (resolvables.length) {
for (const resolvable of resolvables) {
const resolvedValue = this.evaluateExpression(`${resolvable}`, i);
// Use a function replacer to avoid issues with special replacement patterns like $&
if (typeof resolvedValue === 'object' && resolvedValue !== null) {
returnData = returnData.replace(resolvable, () => JSON.stringify(resolvedValue));
} else {
returnData = returnData.replace(resolvable, () => String(resolvedValue));
}
}
}
return returnData;
}
export function prepareReturnItem(
context: IExecuteFunctions | ISupplyDataFunctions,
value: AssignmentCollectionValue,
itemIndex: number,
item: INodeExecutionData,
node: INode,
options: SetNodeOptions,
) {
const jsonValues: AssignmentCollectionValue['assignments'] = [];
const binaryValues: AssignmentCollectionValue['assignments'] = [];
for (const assignment of value?.assignments ?? []) {
if (assignment.type === 'binary') {
binaryValues.push(assignment);
} else {
jsonValues.push(assignment);
}
}
const newData = Object.fromEntries(
jsonValues.map((assignment) => {
const { name, value } = validateEntry(
assignment.name,
assignment.type as FieldType,
assignment.value,
node,
itemIndex,
options.ignoreConversionErrors,
node.typeVersion,
);
return [name, value];
}),
);
const returnItem = composeReturnItem.call(
context,
itemIndex,
item,
newData,
options,
node.typeVersion,
);
if (binaryValues.length) {
if (!returnItem.binary) {
returnItem.binary = {};
}
const { binaryMode } = context.getWorkflowSettings();
const target = binaryMode === BINARY_MODE_COMBINED ? 'json' : 'binary';
const fieldHelper = configureFieldHelper(options.dotNotation);
for (const assignment of binaryValues) {
const name = assignment.name;
const value = assignment.value as string;
const binaryData = context.helpers.assertBinaryData(itemIndex, value);
if (!isBinaryValue(binaryData)) {
throw new NodeOperationError(
node,
`Could not find binary data specified in field ${name}`,
{ itemIndex },
);
}
// Do not push binary data to json if entry contains raw data
if (target === 'json' && !binaryData.id) {
returnItem.binary[name] = binaryData;
continue;
}
if (target === 'json') {
fieldHelper.set(returnItem.json, name, binaryData);
continue;
}
returnItem[target]![name] = binaryData;
}
}
return returnItem;
}
@@ -0,0 +1,248 @@
import type {
AssignmentCollectionValue,
FieldType,
IDataObject,
IExecuteFunctions,
INode,
INodeExecutionData,
INodeProperties,
ISupplyDataFunctions,
} from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import type { SetField, SetNodeOptions } from './helpers/interfaces';
import {
parseJsonParameter,
validateEntry,
composeReturnItem,
resolveRawData,
prepareReturnItem,
} from './helpers/utils';
import { updateDisplayOptions } from '../../../utils/utilities';
const properties: INodeProperties[] = [
{
displayName: 'Fields to Set',
name: 'fields',
placeholder: 'Add Field',
type: 'fixedCollection',
description: 'Edit existing fields or add new ones to modify the output data',
displayOptions: {
show: {
'@version': [3, 3.1, 3.2],
},
},
typeOptions: {
multipleValues: true,
sortable: true,
},
default: {},
options: [
{
name: 'values',
displayName: 'Values',
values: [
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
placeholder: 'e.g. fieldName',
description:
'Name of the field to set the value of. Supports dot-notation. Example: data.person[0].name.',
requiresDataPath: 'single',
},
{
displayName: 'Type',
name: 'type',
type: 'options',
description: 'The field value type',
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
options: [
{
name: 'String',
value: 'stringValue',
},
{
name: 'Number',
value: 'numberValue',
},
{
name: 'Boolean',
value: 'booleanValue',
},
{
name: 'Array',
value: 'arrayValue',
},
{
name: 'Object',
value: 'objectValue',
},
],
default: 'stringValue',
},
{
displayName: 'Value',
name: 'stringValue',
type: 'string',
default: '',
displayOptions: {
show: {
type: ['stringValue'],
},
},
validateType: 'string',
ignoreValidationDuringExecution: true,
},
{
displayName: 'Value',
name: 'numberValue',
type: 'string',
default: '',
displayOptions: {
show: {
type: ['numberValue'],
},
},
validateType: 'number',
ignoreValidationDuringExecution: true,
},
{
displayName: 'Value',
name: 'booleanValue',
type: 'options',
default: 'true',
options: [
{
name: 'True',
value: 'true',
},
{
name: 'False',
value: 'false',
},
],
displayOptions: {
show: {
type: ['booleanValue'],
},
},
validateType: 'boolean',
ignoreValidationDuringExecution: true,
},
{
displayName: 'Value',
name: 'arrayValue',
type: 'string',
default: '',
placeholder: 'e.g. [ arrayItem1, arrayItem2, arrayItem3 ]',
displayOptions: {
show: {
type: ['arrayValue'],
},
},
validateType: 'array',
ignoreValidationDuringExecution: true,
},
{
displayName: 'Value',
name: 'objectValue',
type: 'json',
default: '={}',
typeOptions: {
rows: 2,
},
displayOptions: {
show: {
type: ['objectValue'],
},
},
validateType: 'object',
ignoreValidationDuringExecution: true,
},
],
},
],
},
{
displayName: 'Fields to Set',
name: 'assignments',
type: 'assignmentCollection',
displayOptions: {
hide: {
'@version': [3, 3.1, 3.2],
},
},
default: {},
},
];
const displayOptions = {
show: {
mode: ['manual'],
},
};
export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(
this: IExecuteFunctions | ISupplyDataFunctions,
item: INodeExecutionData,
i: number,
options: SetNodeOptions,
rawFieldsData: IDataObject,
node: INode,
) {
try {
if (node.typeVersion < 3.3) {
const fields = this.getNodeParameter('fields.values', i, []) as SetField[];
const newData: IDataObject = {};
for (const entry of fields) {
if (
entry.type === 'objectValue' &&
rawFieldsData[entry.name] !== undefined &&
entry.objectValue !== undefined &&
entry.objectValue !== null
) {
entry.objectValue = parseJsonParameter(
resolveRawData.call(this, rawFieldsData[entry.name] as string, i),
node,
i,
entry.name,
);
}
const { name, value } = validateEntry(
entry.name,
entry.type.replace('Value', '') as FieldType,
entry[entry.type],
node,
i,
options.ignoreConversionErrors,
node.typeVersion,
);
newData[name] = value;
}
return composeReturnItem.call(this, i, item, newData, options, node.typeVersion);
}
const assignmentCollection = this.getNodeParameter(
'assignments',
i,
) as AssignmentCollectionValue;
return prepareReturnItem(this, assignmentCollection, i, item, node, options);
} catch (error) {
if (this.continueOnFail()) {
return { json: { error: (error as Error).message }, pairedItem: { item: i } };
}
throw new NodeOperationError(this.getNode(), error as Error, {
itemIndex: i,
description: error.description,
});
}
}
@@ -0,0 +1,67 @@
import type {
INodeExecutionData,
IExecuteFunctions,
INodeProperties,
IDataObject,
INode,
} from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import type { SetNodeOptions } from './helpers/interfaces';
import { parseJsonParameter, composeReturnItem, resolveRawData } from './helpers/utils';
import { updateDisplayOptions } from '../../../utils/utilities';
const properties: INodeProperties[] = [
{
displayName: 'JSON',
name: 'jsonOutput',
type: 'json',
typeOptions: {
rows: 5,
},
default: '{\n "my_field_1": "value",\n "my_field_2": 1\n}\n',
validateType: 'object',
ignoreValidationDuringExecution: true,
},
];
const displayOptions = {
show: {
mode: ['raw'],
},
};
export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(
this: IExecuteFunctions,
item: INodeExecutionData,
i: number,
options: SetNodeOptions,
rawData: IDataObject,
node: INode,
) {
try {
let newData: IDataObject;
if (rawData.jsonOutput === undefined) {
const json = this.getNodeParameter('jsonOutput', i) as string;
newData = parseJsonParameter(json, node, i);
} else {
newData = parseJsonParameter(
resolveRawData.call(this, rawData.jsonOutput as string, i),
node,
i,
);
}
return composeReturnItem.call(this, i, item, newData, options, node.typeVersion);
} catch (error) {
if (this.continueOnFail()) {
return { json: { error: (error as Error).message }, pairedItem: { item: i } };
}
throw new NodeOperationError(node, error as Error, {
itemIndex: i,
description: error.description,
});
}
}