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
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:
@@ -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"]
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user