XML to JSON Converter
A Node CLI that converts a small XML document into JSON with attributes under an @ prefix and repeated children grouped into arrays.
Project
node-xml-to-json
Entry point: xml-to-json.mjs
Install
node --versionRun
node xml-to-json.mjs data.xmlTest
node --test xml.test.mjsExpected output
Writes the parsed XML as indented JSON; attributes become @-prefixed keys and repeated elements become arrays.Dependencies
- node >=20
What it does
Tokenizes tags, text, and attributes with a small state machine, builds a tree, then maps it to JSON. Repeated sibling elements collapse into an array, and attributes are prefixed to stay distinct from child elements.
export function xmlToJson(xml) {
const src = xml.replace(/<\?[\s\S]*?\?>/g, '').replace(/<!--[\s\S]*?-->/g, '');
const tokens = src.match(/<[^>]+>|[^<]+/g) ?? [];
const root = {}; const stack = [{ node: root }];
for (const tok of tokens) {
if (tok.startsWith('</')) { stack.pop(); continue; }
if (tok.startsWith('<')) {
const name = tok.match(/^<(\w[\w.-]*)/)?.[1];
const node = {};
for (const attr of tok.matchAll(/(\w[\w.-]*)\s*=\s*"([^"]*)"/g)) node['@' + attr[1]] = attr[2];
const parent = stack[stack.length - 1].node;
if (parent[name] === undefined) { parent[name] = node; }
else if (Array.isArray(parent[name])) { parent[name].push(node); }
else { parent[name] = [parent[name], node]; }
if (!/\/>$/.test(tok)) stack.push({ node });
continue;
}
const text = tok.trim();
if (text) stack[stack.length - 1].node['#text'] = text;
}
return root;
}
import { readFileSync } from 'node:fs';
console.log(JSON.stringify(xmlToJson(readFileSync(process.argv[2], 'utf8')), null, 2));
Run and verify
node xml-to-json.mjs data.xml
Run the test command to check the implementation: node --test xml.test.mjs.
Expected Output
For
{
"user": {
"@id": "7",
"role": ["admin", "dev"]
}
}
Limitations
Does not handle namespaces, CDATA, or mixed content precisely, and a single repeated element with the same name becomes an array only on the second occurrence.