← Runnable Projects

阅读中文版本

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.

advancedjavascriptunverified

Project

node-xml-to-json

Entry point: xml-to-json.mjs

Install

node --version

Run

node xml-to-json.mjs data.xml

Test

node --test xml.test.mjs

Expected 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 admindev the output is:

{
  "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.