User Rating 0.0
Total Usage 0 times
Category JSON Tools
Enter an XML API endpoint. CORS proxy will be used if direct access is blocked.
Paste raw XML content to convert to JSON.
Options
Examples
JSON Output
Processing...
This HTML should be preserved as-is, not parsed.]]>\n 2024-01-15T14:32:00Z\n \n \n [email protected]\n [email protected]\n Weekly Report\n \n 2024-01-15T09:00:00Z\n \n' }; // 1. STATE MANAGEMENT var State = { mode: 'url', url: '', xmlText: '', jsonResult: null, jsonString: '', attrPrefix: '@', textKey: '#text', autoTypes: false, trimWhitespace: true, prettyPrint: true, parseTimeMs: 0, xmlSize: 0, jsonSize: 0 }; // 2. UTILITY FUNCTIONS function saveState() { try { var toSave = { mode: State.mode, url: State.url, xmlText: State.xmlText, attrPrefix: State.attrPrefix, textKey: State.textKey, autoTypes: State.autoTypes, trimWhitespace: State.trimWhitespace, prettyPrint: State.prettyPrint }; localStorage.setItem(STORAGE_KEY, JSON.stringify(toSave)); } catch (e) { // localStorage may be unavailable } } function loadState() { try { var saved = localStorage.getItem(STORAGE_KEY); if (saved) { var parsed = JSON.parse(saved); if (parsed.mode) State.mode = parsed.mode; if (parsed.url !== undefined) State.url = parsed.url; if (parsed.xmlText !== undefined) State.xmlText = parsed.xmlText; if (parsed.attrPrefix !== undefined) State.attrPrefix = parsed.attrPrefix; if (parsed.textKey !== undefined) State.textKey = parsed.textKey; if (parsed.autoTypes !== undefined) State.autoTypes = parsed.autoTypes; if (parsed.trimWhitespace !== undefined) State.trimWhitespace = parsed.trimWhitespace; if (parsed.prettyPrint !== undefined) State.prettyPrint = parsed.prettyPrint; } } catch (e) { // ignore } } function showToast(message, type) { type = type || 'info'; var container = document.querySelector('.tool-ui__toast-container'); if (!container) return; var toast = document.createElement('div'); toast.className = 'tool-ui__toast tool-ui__toast--' + type; toast.textContent = message; container.appendChild(toast); setTimeout(function() { toast.style.animation = 'tool-ui-toast-out 0.3s ease forwards'; setTimeout(function() { if (toast.parentNode) toast.parentNode.removeChild(toast); }, 300); }, 3500); } function isValidUrl(str) { try { var url = new URL(str); return url.protocol === 'http:' || url.protocol === 'https:'; } catch (e) { return false; } } function formatBytes(bytes) { if (bytes === 0) return '0 B'; var k = 1024; var sizes = ['B', 'KB', 'MB', 'GB']; var i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } function fetchWithTimeout(url, timeout) { return new Promise(function(resolve, reject) { var controller = new AbortController(); var timer = setTimeout(function() { controller.abort(); reject(new Error('Request timed out after ' + (timeout / 1000) + 's')); }, timeout); fetch(url, { signal: controller.signal }) .then(function(response) { clearTimeout(timer); if (!response.ok) { reject(new Error('HTTP ' + response.status + ': ' + response.statusText)); } else { resolve(response); } }) .catch(function(err) { clearTimeout(timer); reject(err); }); }); } function fetchXmlFromUrl(url) { return fetchWithTimeout(url, FETCH_TIMEOUT) .then(function(response) { return response.text(); }) .catch(function() { // Try CORS proxies var proxyChain = Promise.reject(new Error('All proxies failed')); CORS_PROXIES.forEach(function(proxyFn) { proxyChain = proxyChain.catch(function() { return fetchWithTimeout(proxyFn(url), FETCH_TIMEOUT) .then(function(response) { return response.text(); }); }); }); return proxyChain; }); } function parseXml(xmlString) { var parser = new DOMParser(); var doc = parser.parseFromString(xmlString, 'application/xml'); var errorNode = doc.querySelector('parsererror'); if (errorNode) { var errorText = errorNode.textContent || 'Unknown XML parse error'; throw new Error('XML Parse Error: ' + errorText.substring(0, 200)); } return doc; } function xmlNodeToJson(node, options) { var attrPrefix = options.attrPrefix; var textKey = options.textKey; var autoTypes = options.autoTypes; var trimWs = options.trimWhitespace; if (node.nodeType === 3 || node.nodeType === 4) { // Text or CDATA var text = node.nodeValue; if (trimWs) text = text.trim(); return text; } if (node.nodeType !== 1) { return null; } var obj = {}; var hasAttributes = node.attributes && node.attributes.length > 0; var hasChildElements = false; var textContent = ''; var childMap = {}; var childOrder = []; // Process attributes if (hasAttributes) { for (var a = 0; a < node.attributes.length; a++) { var attr = node.attributes[a]; var attrVal = attr.value; if (autoTypes) attrVal = autoConvert(attrVal); obj[attrPrefix + attr.name] = attrVal; } } // Process child nodes for (var i = 0; i < node.childNodes.length; i++) { var child = node.childNodes[i]; if (child.nodeType === 3 || child.nodeType === 4) { // Text or CDATA var t = child.nodeValue; if (trimWs) t = t.trim(); if (t.length > 0) { textContent += t; } } else if (child.nodeType === 1) { hasChildElements = true; var childName = child.nodeName; var childValue = xmlNodeToJson(child, options); if (childMap.hasOwnProperty(childName)) { if (!Array.isArray(childMap[childName])) { childMap[childName] = [childMap[childName]]; } childMap[childName].push(childValue); } else { childMap[childName] = childValue; childOrder.push(childName); } } } if (!hasChildElements && !hasAttributes) { // Text-only element, no attributes if (textContent.length === 0) return null; if (autoTypes) return autoConvert(textContent); return textContent; } // Add child elements to obj for (var c = 0; c < childOrder.length; c++) { obj[childOrder[c]] = childMap[childOrder[c]]; } // Add text content if (textContent.length > 0) { var finalText = autoTypes ? autoConvert(textContent) : textContent; if (!hasChildElements && hasAttributes) { obj[textKey] = finalText; } else if (hasChildElements) { obj[textKey] = finalText; } } // Simplify: if only textKey and no attributes, collapse var keys = Object.keys(obj); if (keys.length === 1 && keys[0] === textKey) { return obj[textKey]; } return obj; } function autoConvert(value) { if (typeof value !== 'string') return value; if (value === 'true') return true; if (value === 'false') return false; if (value === 'null') return null; if (value === '') return value; // Check for numeric: must not have leading zeros (except '0' or '0.x') if (/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(value)) { var num = Number(value); if (isFinite(num)) return num; } return value; } function convertXmlToJson(xmlString, options) { var doc = parseXml(xmlString); var root = doc.documentElement; var result = {}; result[root.nodeName] = xmlNodeToJson(root, options); return result; } function syntaxHighlightJson(json) { // Escape HTML entities first var str = json .replace(/&/g, '&') .replace(//g, '>'); return str.replace( /("(?:\\.|[^"\\])*")\s*:|"(?:\\.|[^"\\])*"|\b(?:true|false)\b|\bnull\b|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/g, function(match) { var cls = 'json-number'; if (/^"/.test(match)) { if (/:$/.test(match)) { cls = 'json-key'; } else { cls = 'json-string'; } } else if (/true|false/.test(match)) { cls = 'json-bool'; } else if (/null/.test(match)) { cls = 'json-null'; } return '' + match + ''; } ); } // 3. DOM ELEMENTS var root = document.querySelector('.tool-ui'); if (!root) return; var modeBtns = root.querySelectorAll('.tool-ui__mode-btn'); var urlGroup = root.querySelector('#urlGroup'); var pasteGroup = root.querySelector('#pasteGroup'); var urlInput = root.querySelector('#xmlUrl'); var xmlTextarea = root.querySelector('#xmlInput'); var fetchBtn = root.querySelector('#fetchBtn'); var convertBtn = root.querySelector('#convertBtn'); var outputSection = root.querySelector('#outputSection'); var jsonCode = root.querySelector('#jsonCode'); var outputMeta = root.querySelector('#outputMeta'); var copyBtn = root.querySelector('#copyBtn'); var downloadBtn = root.querySelector('#downloadBtn'); var clearBtn = root.querySelector('#clearBtn'); var loadingIndicator = root.querySelector('#loadingIndicator'); var attrPrefixInput = root.querySelector('#attrPrefix'); var textKeyInput = root.querySelector('#textKey'); var autoTypesCheck = root.querySelector('#autoTypes'); var trimWsCheck = root.querySelector('#trimWhitespace'); var prettyPrintCheck = root.querySelector('#prettyPrint'); var presetBtns = root.querySelectorAll('.tool-ui__preset-btn'); // 4. FUNCTIONS function setMode(mode) { State.mode = mode; modeBtns.forEach(function(btn) { var isActive = btn.getAttribute('data-mode') === mode; btn.classList.toggle('tool-ui__mode-btn--active', isActive); btn.setAttribute('aria-pressed', isActive ? 'true' : 'false'); }); if (mode === 'url') { urlGroup.classList.remove('tool-ui__hidden'); pasteGroup.classList.add('tool-ui__hidden'); } else { urlGroup.classList.add('tool-ui__hidden'); pasteGroup.classList.remove('tool-ui__hidden'); } saveState(); } function showLoading(show) { if (show) { loadingIndicator.classList.remove('tool-ui__hidden'); outputSection.classList.add('tool-ui__hidden'); } else { loadingIndicator.classList.add('tool-ui__hidden'); } } function getOptions() { return { attrPrefix: State.attrPrefix, textKey: State.textKey, autoTypes: State.autoTypes, trimWhitespace: State.trimWhitespace }; } function displayResult(jsonObj) { var indent = State.prettyPrint ? 2 : 0; var jsonStr = JSON.stringify(jsonObj, null, indent); State.jsonResult = jsonObj; State.jsonString = jsonStr; State.jsonSize = new Blob([jsonStr]).size; if (State.prettyPrint) { jsonCode.innerHTML = syntaxHighlightJson(jsonStr); } else { jsonCode.textContent = jsonStr; } outputMeta.textContent = 'XML: ' + formatBytes(State.xmlSize) + ' \u2192 JSON: ' + formatBytes(State.jsonSize) + ' \u00B7 Parsed in ' + State.parseTimeMs + 'ms'; outputSection.classList.remove('tool-ui__hidden'); } function doConvert(xmlString) { if (!xmlString || xmlString.trim().length === 0) { showToast('No XML content to convert.', 'error'); return; } State.xmlSize = new Blob([xmlString]).size; showLoading(true); // Use setTimeout to allow spinner to render setTimeout(function() { try { var start = performance.now(); var result = convertXmlToJson(xmlString, getOptions()); var end = performance.now(); State.parseTimeMs = Math.round(end - start); showLoading(false); displayResult(result); showToast('Converted successfully!', 'success'); } catch (err) { showLoading(false); showToast(err.message || 'Conversion failed.', 'error'); } }, 50); } function handleFetch() { var url = urlInput.value.trim(); if (!url) { showToast('Please enter a URL.', 'error'); urlInput.focus(); return; } if (!isValidUrl(url)) { showToast('Invalid URL format. Include http:// or https://', 'error'); urlInput.focus(); return; } State.url = url; saveState(); fetchBtn.disabled = true; showLoading(true); fetchXmlFromUrl(url) .then(function(xmlText) { fetchBtn.disabled = false; showLoading(false); if (!xmlText || xmlText.trim().length === 0) { showToast('Empty response from server.', 'error'); return; } // Check if it looks like XML var trimmed = xmlText.trim(); if (trimmed.charAt(0) !== '<') { showToast('Response does not appear to be XML. First character: "' + trimmed.charAt(0) + '"', 'error'); return; } State.xmlText = xmlText; doConvert(xmlText); }) .catch(function(err) { fetchBtn.disabled = false; showLoading(false); showToast('Fetch failed: ' + (err.message || 'Unknown error') + '. Try pasting XML directly.', 'error'); }); } function handlePasteConvert() { var xmlText = xmlTextarea.value; if (!xmlText || xmlText.trim().length === 0) { showToast('Please paste XML content.', 'error'); xmlTextarea.focus(); return; } State.xmlText = xmlText; saveState(); doConvert(xmlText); } function handleCopy() { if (!State.jsonString) return; if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(State.jsonString) .then(function() { showToast('JSON copied to clipboard!', 'success'); }) .catch(function() { fallbackCopy(); }); } else { fallbackCopy(); } } function fallbackCopy() { try { var textarea = document.createElement('textarea'); textarea.value = State.jsonString; textarea.style.position = 'fixed'; textarea.style.opacity = '0'; document.body.appendChild(textarea); textarea.select(); document.execCommand('copy'); document.body.removeChild(textarea); showToast('JSON copied to clipboard!', 'success'); } catch (e) { showToast('Failed to copy. Select and copy manually.', 'error'); } } function handleDownload() { if (!State.jsonString) return; try { var blob = new Blob([State.jsonString], { type: 'application/json' }); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = 'converted.json'; a.style.display = 'none'; document.body.appendChild(a); a.click(); setTimeout(function() { URL.revokeObjectURL(url); document.body.removeChild(a); }, 100); showToast('JSON file downloaded.', 'success'); } catch (e) { showToast('Download failed: ' + e.message, 'error'); } } function handleClear() { State.jsonResult = null; State.jsonString = ''; jsonCode.innerHTML = ''; outputMeta.textContent = ''; outputSection.classList.add('tool-ui__hidden'); showToast('Output cleared.', 'info'); } function loadPreset(name) { var xml = PRESETS[name]; if (!xml) return; setMode('paste'); xmlTextarea.value = xml; State.xmlText = xml; saveState(); doConvert(xml); } function syncOptionsFromUI() { State.attrPrefix = attrPrefixInput.value; State.textKey = textKeyInput.value || '#text'; State.autoTypes = autoTypesCheck.checked; State.trimWhitespace = trimWsCheck.checked; State.prettyPrint = prettyPrintCheck.checked; saveState(); // Re-render if we have a result if (State.jsonResult) { // Re-convert from stored XML to pick up option changes if (State.xmlText) { doConvert(State.xmlText); } } } function syncPrettyPrintOnly() { State.prettyPrint = prettyPrintCheck.checked; saveState(); if (State.jsonResult) { displayResult(State.jsonResult); } } // 5. EVENT LISTENERS modeBtns.forEach(function(btn) { btn.addEventListener('click', function() { setMode(btn.getAttribute('data-mode')); }); }); fetchBtn.addEventListener('click', handleFetch); convertBtn.addEventListener('click', handlePasteConvert); copyBtn.addEventListener('click', handleCopy); downloadBtn.addEventListener('click', handleDownload); clearBtn.addEventListener('click', handleClear); urlInput.addEventListener('input', function() { State.url = urlInput.value; saveState(); }); urlInput.addEventListener('keydown', function(e) { if (e.key === 'Enter') { e.preventDefault(); handleFetch(); } }); xmlTextarea.addEventListener('input', function() { State.xmlText = xmlTextarea.value; saveState(); }); attrPrefixInput.addEventListener('change', syncOptionsFromUI); textKeyInput.addEventListener('change', syncOptionsFromUI); autoTypesCheck.addEventListener('change', syncOptionsFromUI); trimWsCheck.addEventListener('change', syncOptionsFromUI); prettyPrintCheck.addEventListener('change', function() { State.prettyPrint = prettyPrintCheck.checked; saveState(); if (State.jsonResult) { displayResult(State.jsonResult); } }); presetBtns.forEach(function(btn) { btn.addEventListener('click', function() { loadPreset(btn.getAttribute('data-preset')); }); }); // Handle paste event for quick convert xmlTextarea.addEventListener('paste', function() { setTimeout(function() { State.xmlText = xmlTextarea.value; saveState(); }, 0); }); // Keyboard shortcuts root.addEventListener('keydown', function(e) { // Ctrl+Enter to convert if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); if (State.mode === 'url') { handleFetch(); } else { handlePasteConvert(); } } }); // 6. INIT loadState(); // Restore UI from state setMode(State.mode); urlInput.value = State.url; xmlTextarea.value = State.xmlText; attrPrefixInput.value = State.attrPrefix; textKeyInput.value = State.textKey; autoTypesCheck.checked = State.autoTypes; trimWsCheck.checked = State.trimWhitespace; prettyPrintCheck.checked = State.prettyPrint; })(); if (document.readyState !== 'loading') { window.dispatchEvent(new Event('DOMContentLoaded')); } } catch (e) { console.error("Critical Error in Tool #2983:", e); } }; runToolScript(); })();
Is this tool helpful?

Your feedback helps us improve.

About

XML APIs still power a significant portion of enterprise integrations, legacy SOAP services, and data feeds (RSS, Atom, TVDB, weather services). Consuming these in modern JavaScript applications requires conversion to JSON. Manual parsing is error-prone: repeated XML elements must become arrays, attributes need a consistent prefix convention (commonly @attr), and mixed content nodes require careful handling of #text keys. A naive regex-based approach will silently corrupt nested structures or lose attribute data. This tool performs recursive DOM tree walking using the browser's native DOMParser, producing spec-compliant JSON that preserves the full information content of the source XML document.

The converter supports two workflows: fetching a live XML API endpoint (routed through a CORS proxy for browser compatibility) or pasting raw XML directly. It handles CDATA sections, XML namespaces, self-closing tags, and processing instructions. Limitations: XML comments are discarded, and extremely large documents (above 50MB) may exceed browser memory constraints. The attribute prefix and text node key are configurable to match your downstream schema requirements.

xml to json api converter xml parser json converter xml api data transformation rest api

Formulas

The core conversion follows a recursive tree-walk algorithm. For each XML element node E, the converter builds a JSON object J by iterating over attributes and child nodes:

convert(E) J = { }
for each attr E.attributes: J[prefix + attr.name] = attr.value
for each child E.childNodes:
if child TEXT CDATA: J[textKey] += child.data
if child ELEMENT:
if J[child.name] exists: wrap in array
else: J[child.name] = convert(child)

Where prefix is the configurable attribute prefix (default @), textKey is the text content key (default #text). Simplification rule: if J contains only textKey and no attributes, collapse to a plain string value. If the element is empty with no attributes, return null.

Reference Data

XML PatternJSON OutputRule Applied
<name>John</name>"name": "John"Text-only element → string value
<item>A</item><item>B</item>"item": ["A", "B"]Repeated siblings → array
<node id="5">val</node>"node": {"@id": "5", "#text": "val"}Attributes use @ prefix
<empty/>"empty": nullSelf-closing → null
<a><b>1</b><c>2</c></a>"a": {"b": "1", "c": "2"}Nested elements → nested object
<![CDATA[raw & data]]>"#text": "raw & data"CDATA preserved as text
<ns:tag xmlns:ns="...">"ns:tag": {...}Namespace prefix retained
<a attr="1"><b>2</b></a>"a": {"@attr": "1", "b": "2"}Mixed attributes + children
<root>text<b>bold</b>more</root>"root": {"#text": "textmore", "b": "bold"}Mixed content merges text nodes
<val>123</val>"val": "123" or 123Optional: auto-detect numbers
<flag>true</flag>"flag": "true" or trueOptional: auto-detect booleans
<?xml version="1.0"?>IgnoredProcessing instructions stripped
<!-- comment -->IgnoredComments discarded
<a><b>1</b><b>2</b><c>3</c></a>"a": {"b": ["1","2"], "c": "3"}Mixed unique + repeated
<a> </a>"a": null (trimmed)Whitespace-only → null (configurable)

Frequently Asked Questions

When the parser encounters a second sibling element with the same tag name, it automatically wraps the existing value and the new value into a JSON array. For example, two siblings become "item": ["val1", "val2"]. This detection happens during the recursive walk, not as a post-processing step, so deeply nested repeated elements are also correctly handled.
Browsers enforce Same-Origin Policy, blocking requests to domains that don't send Access-Control-Allow-Origin headers. This tool routes requests through public CORS proxies (corsproxy.io, allorigins.win) as fallback. If all proxies fail, paste the XML directly into the text input. Server-side XML APIs (SOAP, RSS feeds) rarely set CORS headers since they were designed for server-to-server communication.
The @-prefix convention (e.g., @id, @class) is the most widely adopted standard, used by libraries like xml2js, fast-xml-parser, and BadgerFish. Alternatives include the $ prefix or placing attributes in a separate __attributes__ object. The prefix is configurable in this tool to match your downstream consumer's expectations.
Yes. Namespace-prefixed elements like are converted with their prefix retained as the JSON key: "ns:tag": {...}. Namespace URI declarations (xmlns:ns="...") are treated as attributes and appear as "@xmlns:ns": "...". If you need namespace-free keys, strip prefixes from the output JSON manually.
By default, all XML text content is output as JSON strings, since XML has no type system. Enabling the "Auto-detect types" option will parse values matching numeric patterns (integer or float) as JSON numbers and "true"/"false" as JSON booleans. This is lossy: a zip code like "00123" would become the number 123. Use with caution and only when your schema expects typed values.
Documents under 1 MB parse near-instantly. Between 1-10 MB, the tool offloads parsing to a Web Worker to keep the UI responsive, showing a progress indicator. Above 50 MB, browser memory limits may cause failures. For production-scale batch conversion, a server-side solution (Node.js with streaming XML parser like sax-js) is recommended.