From 15e76d0bcb050c073031ecc69436611bfdcedabb Mon Sep 17 00:00:00 2001 From: nusquama Date: Sun, 15 Mar 2026 12:00:43 +0800 Subject: [PATCH] creation --- ..._and_csv_import_data_via_webhook_with_configurable_rules.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 workflows/Validate JSON and CSV import data via webhook with configurable rules-13999/validate_json_and_csv_import_data_via_webhook_with_configurable_rules.json diff --git a/workflows/Validate JSON and CSV import data via webhook with configurable rules-13999/validate_json_and_csv_import_data_via_webhook_with_configurable_rules.json b/workflows/Validate JSON and CSV import data via webhook with configurable rules-13999/validate_json_and_csv_import_data_via_webhook_with_configurable_rules.json new file mode 100644 index 000000000..fb1a88911 --- /dev/null +++ b/workflows/Validate JSON and CSV import data via webhook with configurable rules-13999/validate_json_and_csv_import_data_via_webhook_with_configurable_rules.json @@ -0,0 +1 @@ +{"id":"63xAsYgwMTW1oqMe","meta":{"instanceId":"a39a10e1017be5c481194e25798c5c17cd990d01d0f9a443239bb35790ba5b46"},"name":"Validate CSV and JSON import data with configurable rules via webhook","tags":[],"nodes":[{"id":"450b896a-7d82-45d9-9a11-4b1e67a8e497","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[0,-128],"parameters":{"color":4,"width":800,"height":588,"content":"## Validate CSV/JSON Import Data with Configurable Rules\n\nThis workflow provides a **reusable data validation API endpoint** for your import pipelines. Send any JSON array of records along with validation rules, and get back a detailed report showing which rows passed and which failed, with specific error messages per field.\n\n### Who is this for?\nOperations teams, data engineers, or anyone importing data into ERP, CRM, databases, or spreadsheets who needs to catch errors **before** they enter the system.\n\n### How it works\n1. Send a POST request with `data` (array of records) and `rules` (validation config)\n2. The workflow validates every field in every row against your rules\n3. Returns a structured JSON report: total/valid/invalid rows + detailed errors\n\n### Supported validation rules\n`required`, `type` (string, number, email, date, url, boolean), `min`, `max`, `minLength`, `maxLength`, `regex`, `enum`, `dateFormat`\n\n### Setup\n1. Activate the workflow\n2. Send a POST request to the webhook URL\n3. Optionally configure default rules in the **Set Default Rules** node\n\n**Author:** Florian Eiche, [eiche-digital.de](https://eiche-digital.de)"},"typeVersion":1},{"id":"0143af87-c46e-4707-8318-fed250e86739","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[0,480],"parameters":{"width":280,"height":328,"content":"### Step 1: Receive Data\nPOST request with JSON body:\n```json\n{\n \"data\": [{...}, {...}],\n \"rules\": {\n \"fieldName\": {\n \"required\": true,\n \"type\": \"email\"\n }\n }\n}\n```\nRules in the request override the defaults."},"typeVersion":1},{"id":"6b3086af-a198-426b-bafc-421d8ac13068","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[320,480],"parameters":{"width":280,"height":328,"content":"### Step 2: Default Rules\nConfigure fallback validation rules here. These are used when the request body does not include a `rules` object.\n\nEdit the JSON in the **Set Default Rules** node to match your data structure."},"typeVersion":1},{"id":"ba474ce4-e4da-47e4-a912-5a320dc60eb5","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[640,480],"parameters":{"width":300,"height":328,"content":"### Step 3: Validate\nThe Code node checks every row against the rules and collects all errors.\n\nSupported checks:\n- `required`\n- `type`: string, number, email, date, url, boolean\n- `min` / `max` (numbers)\n- `minLength` / `maxLength`\n- `regex` (custom pattern)\n- `enum` (allowed values)\n- `dateFormat` (YYYY-MM-DD, DD.MM.YYYY, MM/DD/YYYY)"},"typeVersion":1},{"id":"761e962f-7e78-4418-8bcf-814c0e67c8cc","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[992,416],"parameters":{"width":296,"height":392,"content":"### Step 4: Response\nReturns a JSON report:\n```json\n{\n \"valid\": false,\n \"summary\": {\n \"totalRows\": 3,\n \"validRows\": 1,\n \"invalidRows\": 2,\n \"totalErrors\": 4\n },\n \"errors\": [\n {\n \"row\": 2,\n \"field\": \"email\",\n \"value\": \"invalid\",\n \"rule\": \"type:email\",\n \"message\": \"...\"\n }\n ]\n}\n```"},"typeVersion":1},{"id":"9cba0c0c-f991-4113-b267-3577c6a77698","name":"Receive Data","type":"n8n-nodes-base.webhook","position":[112,848],"webhookId":"4344cc96-7296-40c8-ae0a-0383ee05dfe5","parameters":{"path":"validate-data","options":{},"httpMethod":"POST","responseMode":"responseNode"},"typeVersion":2.1},{"id":"29a55404-f6ac-47e3-870c-e9d4edde4b9a","name":"Set Default Rules","type":"n8n-nodes-base.set","position":[432,848],"parameters":{"mode":"raw","options":{},"jsonOutput":"{\n \"rules\": {\n \"name\": {\n \"required\": true,\n \"type\": \"string\",\n \"minLength\": 2,\n \"maxLength\": 100\n },\n \"email\": {\n \"required\": true,\n \"type\": \"email\"\n },\n \"age\": {\n \"required\": false,\n \"type\": \"number\",\n \"min\": 0,\n \"max\": 150\n },\n \"status\": {\n \"required\": true,\n \"type\": \"string\",\n \"enum\": [\"active\", \"inactive\", \"pending\"]\n },\n \"website\": {\n \"required\": false,\n \"type\": \"url\"\n },\n \"joinDate\": {\n \"required\": false,\n \"type\": \"date\",\n \"dateFormat\": \"YYYY-MM-DD\"\n }\n }\n}"},"typeVersion":3.4},{"id":"38d23008-b445-4885-be49-c76b0478e877","name":"Validate Data","type":"n8n-nodes-base.code","position":[752,848],"parameters":{"jsCode":"// Get input data and rules\nconst input = $('Receive Data').first().json.body;\nconst defaults = $('Set Default Rules').first().json;\n\nconst data = input.data;\nconst rules = input.rules || defaults.rules || {};\n\n// Validate input\nif (!data || !Array.isArray(data)) {\n return [{\n json: {\n valid: false,\n error: 'Invalid input: \"data\" must be an array of objects.',\n summary: { totalRows: 0, validRows: 0, invalidRows: 0, totalErrors: 1 },\n errors: []\n }\n }];\n}\n\nif (!rules || Object.keys(rules).length === 0) {\n return [{\n json: {\n valid: false,\n error: 'No validation rules provided. Send rules in the request body or configure them in the Set Default Rules node.',\n summary: { totalRows: data.length, validRows: 0, invalidRows: 0, totalErrors: 1 },\n errors: []\n }\n }];\n}\n\n// Validation helpers\nconst isPresent = (v) => v !== null && v !== undefined && v !== '';\nconst isEmail = (v) => /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(String(v));\nconst isNumber = (v) => !isNaN(Number(v)) && String(v).trim() !== '';\nconst isUrl = (v) => /^https?:\\/\\/.+\\..+/.test(String(v));\nconst isBool = (v) => ['true','false','0','1','yes','no'].includes(String(v).toLowerCase());\n\nfunction isValidDate(v, fmt) {\n if (fmt === 'YYYY-MM-DD') return /^\\d{4}-\\d{2}-\\d{2}$/.test(v);\n if (fmt === 'DD.MM.YYYY') return /^\\d{2}\\.\\d{2}\\.\\d{4}$/.test(v);\n if (fmt === 'MM/DD/YYYY') return /^\\d{2}\\/\\d{2}\\/\\d{4}$/.test(v);\n return !isNaN(Date.parse(v));\n}\n\nconst errors = [];\nlet validRows = 0;\nlet invalidRows = 0;\n\nfor (let i = 0; i < data.length; i++) {\n const row = data[i];\n let rowHasError = false;\n\n for (const [field, fr] of Object.entries(rules)) {\n const value = row[field];\n\n // Required check\n if (fr.required && !isPresent(value)) {\n errors.push({\n row: i + 1, field, value: value ?? null,\n rule: 'required',\n message: `Field '${field}' is required`\n });\n rowHasError = true;\n continue;\n }\n\n // Skip further checks if empty and not required\n if (!isPresent(value)) continue;\n\n // Type checks\n if (fr.type) {\n let typeValid = true;\n let typeLabel = fr.type;\n\n switch (fr.type) {\n case 'string':\n typeValid = typeof value === 'string';\n break;\n case 'number':\n typeValid = isNumber(value);\n break;\n case 'email':\n typeValid = isEmail(value);\n break;\n case 'url':\n typeValid = isUrl(value);\n break;\n case 'boolean':\n typeValid = isBool(value);\n break;\n case 'date':\n typeValid = isValidDate(value, fr.dateFormat);\n typeLabel = fr.dateFormat ? `date (${fr.dateFormat})` : 'date';\n break;\n }\n\n if (!typeValid) {\n errors.push({\n row: i + 1, field, value,\n rule: `type:${fr.type}`,\n message: `Field '${field}' must be a valid ${typeLabel}`\n });\n rowHasError = true;\n }\n }\n\n // String length checks\n if (fr.minLength !== undefined && String(value).length < fr.minLength) {\n errors.push({\n row: i + 1, field, value,\n rule: 'minLength',\n message: `Field '${field}' must be at least ${fr.minLength} characters`\n });\n rowHasError = true;\n }\n\n if (fr.maxLength !== undefined && String(value).length > fr.maxLength) {\n errors.push({\n row: i + 1, field, value,\n rule: 'maxLength',\n message: `Field '${field}' must be at most ${fr.maxLength} characters`\n });\n rowHasError = true;\n }\n\n // Numeric range checks\n if (fr.min !== undefined && isNumber(value) && Number(value) < fr.min) {\n errors.push({\n row: i + 1, field, value,\n rule: 'min',\n message: `Field '${field}' must be >= ${fr.min}`\n });\n rowHasError = true;\n }\n\n if (fr.max !== undefined && isNumber(value) && Number(value) > fr.max) {\n errors.push({\n row: i + 1, field, value,\n rule: 'max',\n message: `Field '${field}' must be <= ${fr.max}`\n });\n rowHasError = true;\n }\n\n // Regex check\n if (fr.regex && !new RegExp(fr.regex).test(String(value))) {\n errors.push({\n row: i + 1, field, value,\n rule: 'regex',\n message: `Field '${field}' does not match pattern: ${fr.regex}`\n });\n rowHasError = true;\n }\n\n // Enum check\n if (fr.enum && !fr.enum.includes(value)) {\n errors.push({\n row: i + 1, field, value,\n rule: 'enum',\n message: `Field '${field}' must be one of: ${fr.enum.join(', ')}`\n });\n rowHasError = true;\n }\n }\n\n if (rowHasError) invalidRows++;\n else validRows++;\n}\n\nreturn [{\n json: {\n valid: errors.length === 0,\n summary: {\n totalRows: data.length,\n validRows,\n invalidRows,\n totalErrors: errors.length\n },\n errors\n }\n}];"},"typeVersion":2},{"id":"3506f389-3a17-43ad-aa6f-7a2b0774943e","name":"Respond with Report","type":"n8n-nodes-base.respondToWebhook","position":[1072,848],"parameters":{"options":{"responseHeaders":{"entries":[{"name":"Content-Type","value":"application/json"}]}},"respondWith":"json","responseBody":"={{ $json }}"},"typeVersion":1.5}],"active":true,"pinData":{},"settings":{"binaryMode":"separate","availableInMCP":false,"executionOrder":"v1"},"versionId":"312a0c2e-7262-4648-b1c4-f42cba4fd2a1","connections":{"Receive Data":{"main":[[{"node":"Set Default Rules","type":"main","index":0}]]},"Validate Data":{"main":[[{"node":"Respond with Report","type":"main","index":0}]]},"Set Default Rules":{"main":[[{"node":"Validate Data","type":"main","index":0}]]}}} \ No newline at end of file