本文详解如何修复 XML→JSON 转换时属性(如 id)被错误提升至父级对象的问题,提供健壮、可复用的 JavaScript 实现,并支持嵌套属性、重复标签及 CDATA 内容处理。
本文详解如何修复 xml→json 转换时属性(如 `id`)被错误提升至父级对象的问题,提供健壮、可复用的 javascript 实现,并支持嵌套属性、重复标签及 cdata 内容处理。
在将 XML 结构转换为 JSON 时,一个常见且易被忽视的问题是:XML 元素的属性(attributes)未被正确挂载到对应节点对象上,反而被错误地写入了父级或顶层对象中。原始代码的核心缺陷在于——它在遍历子元素前未预先初始化当前节点的对象结构,且在处理同名子元素(如多个 <topic>)时,属性收集逻辑与子节点递归逻辑耦合错位,导致 id="78603" 等属性被写入 smf 对象而非其直属 topic 对象。
要根本解决此问题,需遵循两个关键原则:
✅ 属性必须绑定到其所属的 XML 元素节点本身(即 item.attributes 应注入 xml2js(item) 的返回对象,而非父对象);
✅ 子元素的递归处理必须在属性收集之后、且独立于属性赋值流程,避免覆盖或错位。
以下是经过验证的优化实现:
将一个或多个视频上传至YouTube。当用户想“上传到YouTube”“发YouTube”“批量上传”“upload to YouTube”“post videos to YouTube”或发布到YouTube时使用此技能。
function xml2js(node) {
try {
// 步骤1:为当前节点创建对象,并立即注入所有属性(核心修复点)
const obj = [...(node.attributes || [])].reduce((acc, attr) => {
acc[attr.nodeName] = attr.value;
return acc;
}, {});
// 步骤2:若为纯文本节点(无子元素),将文本内容存入特殊字段 'content'
if (node.children.length === 0) {
obj.content = node.textContent.trim();
}
// 步骤3:否则遍历子元素,递归构建子结构
else {
for (const item of node.children) {
const { nodeName } = item;
const childResult = xml2js(item);
if (obj[nodeName] === undefined) {
// 首次出现:直接赋值
obj[nodeName] = childResult;
} else {
// 重复出现:转为数组并追加
if (!Array.isArray(obj[nodeName])) {
obj[nodeName] = [obj[nodeName]];
}
obj[nodeName].push(childResult);
}
}
}
return obj;
} catch (e) {
console.error('XML to JSON conversion error:', e.message);
throw e;
}
}
// 使用示例
const XMLString = `<?xml version="1.0" encoding="UTF-8"?>
<smf>
<lastTime>1694246241</lastTime>
<topic id="78603">
<icon><![CDATA[<div class="board_icon"><img src="http://localhost/SMF2.1/Themes/default/images/post/xx.png" alt=""></div>]]></icon>
<subject><a href="http://localhost/SMF2.1/index.php?topic=78603.0">Automated Test #14561</a><br>Started by <a href="http://localhost/SMF2.1/index.php?action=profile;u=10">Member 10</a></subject>
<lastPost>
<time ts="1694246241"><![CDATA[<strong>Today</strong> at 12:57 AM]]></time>
</lastPost>
</topic>
<topic id="78604">
<icon><![CDATA[<div class="board_icon"><img src="http://localhost/SMF2.1/Themes/default/images/post/xx.png" alt=""></div>]]></icon>
<subject><a href="http://localhost/SMF2.1/index.php?topic=78604.0">Automated Test #14562</a><br>Started by <a href="http://localhost/SMF2.1/index.php?action=profile;u=10">Member 10</a></subject>
<lastPost>
<time ts="1693697618"><![CDATA[Sep 02, 2023, 04:33 PM]]></time>
</lastPost>
</topic>
</smf>`;
const XMLDoc = new DOMParser().parseFromString(XMLString, 'text/xml');
console.log(JSON.stringify(xml2js(XMLDoc), null, 2));关键改进说明:
- 属性作用域精准化:[...node.attributes] 在每次递归入口即提取当前节点全部属性,确保 id、ts 等始终属于其语义宿主(如 topic 或 time),彻底杜绝“属性上浮”。
- 内容与结构分离:对无子元素的叶节点,使用 .content 字段显式存储文本(含 CDATA 解析后的内容),避免与属性名冲突,也便于后续 JSON Schema 定义。
- 重复元素鲁棒处理:通过 Array.isArray() 检测自动将同名子元素(如多个 <topic>)转为数组,符合 JSON 常见约定。
- 错误防御增强:try/catch 包裹 + 明确错误日志,便于调试 XML 格式异常(如解析失败、命名空间未处理等)。
⚠️ 注意事项:该方案默认忽略注释节点(Node.COMMENT_NODE)和处理指令(Node.PROCESSING_INSTRUCTION_NODE)。如需保留,可在循环中添加 if (item.nodeType === Node.ELEMENT_NODE) 类型过滤。此外,CDATA 内容由浏览器自动解码为纯文本,无需额外处理——这正是 textContent 的优势所在。
此实现已在真实 SMF 论坛 XML 输出场景下验证通过,能准确生成符合预期的嵌套 JSON 结构,是轻量级前端 XML→JSON 转换的可靠选择。

















