{
  "nodes": [
    {
      "id": "61e73730-2e9b-4882-82d0-b218958c0dc1",
      "name": "Convert JSON to XML",
      "type": "n8n-nodes-base.code",
      "position": [
        160,
        48
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// --- Configuration ---\nconst REMOVE_EMPTY_VALUES = true; // Omit empty tags\n\n// Newline configuration (number of '\\n')\nconst NEWLINES_TOP_LEVEL = 2;         // Between top-level elements (e.g., 2 for \\n\\n)\nconst NEWLINES_ARRAY_ITEMS = 2;       // Between <0>, <1>... elements of a complex array\nconst NEWLINES_OBJECT_PROPERTIES = 1; // Between <key1>, <key2>... properties of an object\nconst NEWLINES_WITHIN_TAGS = 1;       // Between opening/closing tag and its content (0, 1, 2...)\n\n// --- Input Retrieval ---\nconst inputJson = $input.item.json;\n\n// --- Utility Functions ---\n\n/**\n * Generates a string containing the specified number of newlines.\n * @param {number} count The desired number of newlines.\n * @returns {string} The newline string (e.g., \"\\n\", \"\\n\\n\").\n */\nfunction generateNewlines(count) {\n  return '\\n'.repeat(Math.max(0, count)); // Ensures a non-negative count\n}\n\n/**\n * Attempts to detect and format a value into a readable date (YYYY-MM-DD HH:mm:ss).\n * @param {any} value The value to check and format.\n * @returns {string|any} The formatted date if detected, otherwise the original value.\n */\nfunction formatIfDate(value) {\n  const originalValue = value;\n  let date = null;\n  try {\n    if (value instanceof Date && !isNaN(value.getTime())) {\n      date = value;\n    }\n    /* // Numeric timestamp detection (COMMENTED OUT)\n    else if (typeof value === 'number') { ... } */\n    else if (typeof value === 'string') {\n      // Basic check for string length and presence of digits/date separators\n      if (value.length >= 8 && /[0-9]/.test(value) && /[-/T:.\\s]/.test(value)) {\n        const parsedDate = new Date(value);\n        if (!isNaN(parsedDate.getTime())) {\n          date = parsedDate;\n        }\n      }\n    }\n    if (date instanceof Date && !isNaN(date.getTime())) {\n      const year = date.getFullYear();\n      const month = String(date.getMonth() + 1).padStart(2, '0');\n      const day = String(date.getDate()).padStart(2, '0');\n      const hours = String(date.getHours()).padStart(2, '0');\n      const minutes = String(date.getMinutes()).padStart(2, '0');\n      const seconds = String(date.getSeconds()).padStart(2, '0');\n      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;\n    }\n  } catch (e) {\n    console.warn(`Error formatting date: ${value}`, e);\n    return originalValue;\n  }\n  return originalValue;\n}\n\n\n/**\n * Finds a safe delimiter to join elements of a primitive array.\n * @param {Array<any>} arr The array whose elements will be joined.\n * @returns {string} The found delimiter.\n */\nfunction findArrayDelimiter(arr) {\n  const stringElements = arr.map(el => String(el === null || el === undefined ? '' : el));\n  let delimiters = [',', '|'];\n  // Try common delimiters first\n  for (const d of delimiters) {\n    if (!stringElements.some(s => s.includes(d))) {\n      return d;\n    }\n  }\n  // If common delimiters are found in elements, create a unique one\n  let currentDelimiter = '||';\n  while (stringElements.some(s => s.includes(currentDelimiter))) {\n    currentDelimiter += '|';\n    if (currentDelimiter.length > 20) {\n      console.warn(\"Long delimiter used:\", arr);\n      return currentDelimiter; // Prevent infinite loop for extremely complex cases\n    }\n  }\n  return currentDelimiter;\n}\n\n/**\n * Checks if a value is a primitive (string, number, boolean, null, undefined).\n * @param {any} value The value to check.\n * @returns {boolean} True if it's a primitive.\n */\nfunction isPrimitive(value) {\n  const type = typeof value;\n  return value === null || type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean';\n}\n\n\n/**\n * Recursive function to convert a JSON value into an XML fragment.\n * Uses configuration parameters for newlines.\n * Does NOT use XML escaping.\n * @param {any} value The JSON value to convert.\n * @param {string} tagName The XML tag name.\n * @returns {string} The generated XML fragment, or an empty string if the value is empty and should be omitted.\n */\nfunction jsonToXmlRecursive(value, tagName) {\n  const openTag = `<${tagName}>`;\n  const closeTag = `</${tagName}>`;\n\n  let processedValue = value;\n\n  // 1. Attempt to parse JSON/Array strings\n  if (typeof value === 'string') {\n    const trimmedValue = value.trim();\n    if (trimmedValue.startsWith('{') || trimmedValue.startsWith('[')) {\n      try {\n        processedValue = JSON.parse(trimmedValue);\n        console.log(`String parsed for tag: '${tagName}'.`);\n      } catch (e) {\n        processedValue = value; // Keep the original string if parsing fails\n        console.warn(`Parsing failed for tag: '${tagName}'. Error: ${e.message}`);\n      }\n    } else {\n      processedValue = trimmedValue; // Use the trimmed version for further processing\n    }\n  }\n\n  // 2. Initial check for null/undefined values\n  if (processedValue === null || processedValue === undefined) {\n    return REMOVE_EMPTY_VALUES ? '' : `${openTag}${closeTag}`;\n  }\n\n  // 3. Process based on type and calculate innerContent\n  let innerContent = '';\n  const nlArrayItemSep = generateNewlines(NEWLINES_ARRAY_ITEMS);\n  const nlObjectPropSep = generateNewlines(NEWLINES_OBJECT_PROPERTIES);\n\n  if (Array.isArray(processedValue)) {\n    if (processedValue.length === 0) {\n      return REMOVE_EMPTY_VALUES ? '' : `${openTag}${closeTag}`;\n    }\n\n    const elementsAfterDateFormatting = processedValue.map(el => formatIfDate(el));\n    const allPrimitives = elementsAfterDateFormatting.every(el => isPrimitive(el));\n\n    if (allPrimitives) { // Array of primitives -> join\n      const delimiter = findArrayDelimiter(elementsAfterDateFormatting);\n      const joinedString = elementsAfterDateFormatting\n        .map(el => String(el === null || el === undefined ? '' : el))\n        .join(delimiter)\n        .trim();\n      if (REMOVE_EMPTY_VALUES && joinedString === '') return '';\n      innerContent = joinedString;\n    } else { // Complex/mixed array -> iterate and join with configured separator\n      const innerParts = [];\n      processedValue.forEach((item, index) => {\n        const itemXml = jsonToXmlRecursive(item, String(index)); // Use index as tag name for array items\n        if (itemXml !== '') innerParts.push(itemXml);\n      });\n      if (REMOVE_EMPTY_VALUES && innerParts.length === 0) return '';\n      innerContent = innerParts.join(nlArrayItemSep); // Uses array item separator\n    }\n  } else if (typeof processedValue === 'object') {\n    if (Object.keys(processedValue).length === 0) {\n      return REMOVE_EMPTY_VALUES ? '' : `${openTag}${closeTag}`;\n    }\n    // Object -> iterate and join with configured separator\n    const innerParts = [];\n    for (const key in processedValue) {\n      if (Object.prototype.hasOwnProperty.call(processedValue, key)) {\n        const propertyXml = jsonToXmlRecursive(processedValue[key], key);\n        if (propertyXml !== '') innerParts.push(propertyXml);\n      }\n    }\n    if (REMOVE_EMPTY_VALUES && innerParts.length === 0) return '';\n    innerContent = innerParts.join(nlObjectPropSep); // Uses object property separator\n  } else { // Primitive\n    const finalValue = formatIfDate(processedValue);\n    const stringValue = String(finalValue === null || finalValue === undefined ? '' : finalValue).trim();\n    if (REMOVE_EMPTY_VALUES && stringValue === '') return '';\n    innerContent = stringValue;\n  }\n\n  // 4. Construct the final XML for this value WITH configured newlines\n  const nlWithin = generateNewlines(NEWLINES_WITHIN_TAGS);\n  if (innerContent === '') {\n    // If content is empty, no internal newlines\n    return `${openTag}${closeTag}`;\n  } else {\n    // If there is content, add the configured internal newlines\n    return `${openTag}${nlWithin}${innerContent}${nlWithin}${closeTag}`;\n  }\n}\n\n// --- Main Conversion Logic ---\n\nlet finalXmlParts = [];\nconst nlTopLevelSep = generateNewlines(NEWLINES_TOP_LEVEL); // Separator for level 1\n\nif (inputJson && typeof inputJson === 'object' && !Array.isArray(inputJson)) {\n  // If the input is a direct object, iterate its properties as top-level elements\n  for (const topLevelKey in inputJson) {\n    if (Object.prototype.hasOwnProperty.call(inputJson, topLevelKey)) {\n      const part = jsonToXmlRecursive(inputJson[topLevelKey], topLevelKey);\n      if (part !== '') finalXmlParts.push(part);\n    }\n  }\n} else if (inputJson !== null && inputJson !== undefined) {\n  // If the input is a primitive or an array, wrap it in a <root> tag\n  console.warn(\"Non-object input. Encapsulating in <root>.\");\n  const part = jsonToXmlRecursive(inputJson, \"root\");\n  if (part !== '') {\n    finalXmlParts.push(part);\n  } else {\n    console.warn(\"Entire input is empty and omitted.\");\n  }\n} else {\n  console.warn(\"Empty/invalid JSON input.\");\n}\n\n// Join the top-level XML fragments with the configured separator\nconst finalXmlString = finalXmlParts.join(nlTopLevelSep);\n\n// --- Output Preparation ---\nreturn {\n  xml: finalXmlString\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "8206f932-d658-41db-a8bc-50568d18f495",
      "name": "JSON to XML",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "position": [
        -128,
        48
      ],
      "parameters": {
        "inputSource": "passthrough"
      },
      "typeVersion": 1.1
    },
    {
      "id": "9e3350b9-b6ad-4993-8cef-995ff73eb4cd",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -704,
        -624
      ],
      "parameters": {
        "color": 7,
        "width": 688,
        "height": 824,
        "content": "## JSON to XML Converter (Code Node)\n\nThis workflow utilizes a custom Code node to transform a JSON input into an XML structure. It offers flexibility in formatting and handling empty values.\n\n### Key Features:\n*   **Recursive conversion** from JSON to XML.\n*   **Array Handling:**\n    *   Primitive arrays are **joined with a safe delimiter** (e.g., `val1,val2`).\n    *   Complex arrays are converted into `<0>`, `<1>`, etc., tags.\n*   **Date detection and formatting** (YYYY-MM-DD HH:mm:ss).\n*   **Attempted parsing** of JSON/Array strings contained within values.\n*   **Encapsulates non-object input** within a `<root>` tag.\n\n---\n\n### Input:\n\n*   A single item (process each item in the subworkflow).\n\n### Output:\n\n*   An object with an `xml` property containing the generated XML string, very useful for AI prompts.\n\n---\n\n### Automate your operations today\nYour time is valuable. Let us automate the boring stuff for you.\n\n**\ud83d\udc47 CHOOSE YOUR PATH:**\n\n[ **\u26a1\ufe0f I WANT A FREE AUDIT (2 min)** ](https://workflows.ac/audit?utm_source=n8n_template&utm_medium=workflow_note&utm_campaign=transform_json_to_xml_for_enhanced_ai_prompt_formatting&utm_content=5144)\n> *We've put our heart into this business evaluation machine.*\n\n[ **\ud83d\udca1 I HAVE A SPECIFIC REQUEST** ](https://workflows.ac/form?utm_source=n8n_template&utm_medium=workflow_note&utm_campaign=transform_json_to_xml_for_enhanced_ai_prompt_formatting&utm_content=5144)\n"
      },
      "typeVersion": 1
    },
    {
      "id": "0e34e239-4490-44cc-8531-8869df671b90",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32,
        -480
      ],
      "parameters": {
        "color": 6,
        "width": 400,
        "height": 680,
        "content": "### Configuration (within the code):\n\n*   **`REMOVE_EMPTY_VALUES`** (boolean): Omits XML tags if their content is empty (default: `true`).\n*   **Configurable Newlines** (`NEWLINES_TOP_LEVEL`, `NEWLINES_ARRAY_ITEMS`, `NEWLINES_OBJECT_PROPERTIES`, `NEWLINES_WITHIN_TAGS`): Controls indentation and readability of the generated XML.\n\n---\n\n### \u26a0\ufe0f IMPORTANT / NOTE \u26a0\ufe0f:\n\n*   **No XML escaping for special characters** (e.g., <, >, &, ', \"). If your JSON contains these characters in values, they will be inserted as-is into the XML, which might render the XML invalid or vulnerable. A manual escaping step might be necessary if this is an issue.\n*   This node is a custom script. Any modifications or maintenance must be done directly within the code."
      },
      "typeVersion": 1
    },
    {
      "id": "442907fb-24f2-4f7b-9372-ad14206d4e20",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        480,
        -944
      ],
      "parameters": {
        "color": 7,
        "width": 540,
        "height": 1136,
        "content": "## Was this helpful? Let me know!\n[![clic](https://supastudio.ia2s.app/storage/v1/object/public/assets/n8n/clic_down_lucas.gif)](https://api.ia2s.app/form/templates/academy)\n\nI really hope this template helped you. Your feedback is what helps me create better resources for the n8n community.\n\n### **Have Feedback, a Question, or a Project Idea?**\n\nI've streamlined the way we connect. It all starts with one simple form that takes less than 15 seconds.\n\n#### \u27a1\ufe0f **[Click here to go to the Contact Form](https://workflows.ac/form?utm_source=n8n_template&utm_medium=workflow_note&utm_campaign=transform_json_to_xml_for_enhanced_ai_prompt_formatting&utm_content=5144)**\n\nUse this single link for anything you need:\n\n*   **Give Feedback:** Share your thoughts on this template, whether you found a typo, encountered an unexpected error, have a suggestion, or just want to say thanks!\n\n*   **Automation Coaching:** Get personalized, one-on-one guidance to master n8n. We can work together to help you reach an expert level.\n\n*   **Automation Consulting:** Have a complex business challenge or need custom workflows built from scratch? We offer a plug and play automation department for 8 to 88 people teams with unlimited automation requests.\n\n---\n\nHappy Automating!\nLucas Peyrin | [Workflows Accelerator](https://workflows.ac?utm_source=n8n_template&utm_medium=workflow_note&utm_campaign=transform_json_to_xml_for_enhanced_ai_prompt_formatting&utm_content=5144)"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "JSON to XML": {
      "main": [
        [
          {
            "node": "Convert JSON to XML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}