XML到JSON的最简单映射方法

1次阅读

xml转扁平jsON首选xml2js(ignoreAttrs: true),禁用json.stringify(XMLDocument);复杂场景用fast-xml-parser;浏览器端可dom API应急但限制多。

XML到JSON的最简单映射方法

XML字符串直接转为扁平JSON对象(无嵌套)

如果XML结构简单、没有重复标签、不关心属性和文本混合,xml2js 的默认解析器配合 ignoreAttrs: truemergeAttrs: false 是最轻量的路径。它把每个元素转成键值对,值是其文本内容,忽略所有属性。

常见错误:直接用 JSON.stringify(xmlDoc) —— 浏览器原生 XMLDocument 对象不能这样序列化,会得到空对象或报错 TypeError: Converting circular structure to JSON

  • 适用场景:配置文件、单层API响应(如 ok200
  • 不适用:含同名子节点(如多个 )、含属性()、含 CDATA 或注释
  • 性能影响:小XML(xml2js.Parser({ async: true }))

保留属性和文本内容的通用映射(推荐默认方案)

真实XML往往含属性(idtype)和混合内容(文本+子节点),此时必须区分 #text@attributes。用 xml2js 配置 explicitArray: falseexplicitChildren: true 可避免冗余数组包装,让结构更贴近直觉。

const xml2js = require('xml2js'); const parser = new xml2js.Parser({   explicitArray: false,   explicitChildren: true,   childkey: '$',   charsAsChildren: true,   mergeAttrs: true });  // 输入:Tom30 // 输出:{ user: { $: { id: "123" }, name: "Tom", age: "30" } }

关键点:

  • $ 键固定存放属性(由 childkey: '$' 指定),避免与子元素名冲突
  • charsAsChildren: true 确保纯文本节点也作为子项出现,否则顶层文本会被丢弃
  • 若XML有多个同名子节点(如 AB),explicitArray: false 会把它变成对象而非数组 —— 这是坑,需手动检测类型再归一化

处理重复子节点( 多次出现)

XML中重复标签是常见痛点:xml2js 默认把多个 合并为一个对象(只保留最后一个),除非启用 explicitArray: true。但开启后所有子节点都变数组,哪怕只有一个 —— 不够干净。

折中做法:保持 explicitArray: false,解析后递归扫描值是否为数组,对已知重复标签(如 itementry)做后处理:

function normalizeRepeated(obj, keys = ['item', 'entry']) {   if (obj == null || typeof obj !== 'object') return obj;   for (const key of keys) {     if (Array.isArray(obj[key])) continue;     if (obj[key] !== undefined && !Array.isArray(obj[key])) {       obj[key] = [obj[key]];     }   }   Object.keys(obj).forEach(k => obj[k] = normalizeRepeated(obj[k], keys));   return obj; }

注意:

  • 必须在 parser.parseString 回调里调用,不能对原始XML字符串操作
  • 如果XML层级深且重复标签多,此函数需加循环深度限制,否则溢出
  • 浏览器端可用 fast-xml-parser 替代,它原生支持 ignoreAttributes: false + arrayMode: 'strict',无需后处理

浏览器环境零依赖方案(仅限简单XML)

不想引入 xml2jsfast-xml-parser?DOM API 可应急,但仅适用于格式良好、无命名空间、无DOCTYPE 声明的XML字符串。

步骤:用 DOMParser 解析 → 递归遍历 Element 节点 → 手动构造JSON对象。核心逻辑如下:

function xmlToJSON(xmlStr) {   const doc = new DOMParser().parseFromString(xmlStr, 'application/xml');   if (doc.querySelector('parsererror')) throw new Error('Invalid XML');      function walk(node) {     if (node.nodeType !== Node.ELEMENT_NODE) return null;          const obj = {};     // 属性     for (let attr of node.attributes) {       obj['@' + attr.name] = attr.value;     }     // 子节点文本或元素     let children = Array.from(node.childNodes)       .map(n => n.nodeType === Node.TEXT_NODE ? n.textContent.trim() : walk(n))       .filter(x => x != null && x !== '');          if (children.length === 0) {       // 纯空元素,返回 null 或 '',按需调整       return obj;     }     if (children.length === 1 && typeof children[0] === 'string') {       obj['#text'] = children[0];     } else {       Object.assign(obj, Object.assign({}, ...children));     }     return { [node.nodeName]: obj };   }   return walk(doc.documentElement); }

限制明显:

  • 无法处理命名空间( 会被当成 ns:item 键名)
  • 属性名加 @ 前缀是惯例,但和 xml2js$ 不兼容,跨环境需统一约定
  • 遇到 时,textContent 会包含 字符串,需正则清洗

真正复杂或高频的XML/JSON互转,别省那几十KB包体积——选 fast-xml-parser,它的 parse 方法默认就处理重复标签、属性、CDATA 和注释,且无依赖、typescript友好。手写解析器容易在边缘情况翻车,比如自闭合标签 XML到JSON的最简单映射方法 或带前导空格的文本节点。

text=ZqhQzanResources