Node.js中1.8GB大XML文件转JSON的流式解析问题
大XML文件流式转JSON解决方案
原代码问题分析
XMLParser不支持流式解析:fast-xml-parser的XMLParser是一次性解析完整XML的实例,每次调用parse都会重置内部状态,分块喂数据会导致内部状态混乱,触发addChild相关的错误。- 文件写入逻辑错误:
fs.writeFileSync每次都会覆盖文件内容,所以最终只会保留第一块解析结果。 - 不完整节点处理失效:sax的
data事件返回的是原始字符串块,可能包含不完整的XML节点,直接传给XMLParser会导致解析异常。
方案一:使用fast-xml-parser原生流式解析器
fast-xml-parser提供了XMLStreamParser支持流式处理,无需依赖sax,直接对接文件流即可:
import fs from 'fs'; import { XMLStreamParser } from 'fast-xml-parser'; // 解析配置保持原逻辑 const parserOptions = { ignoreDeclaration: true, ignoreAttributes: false, removeNSPrefix: true, attributeNamePrefix: "", transformTagName: (tagname) => camelize(tagname), transformAttributeName: (attributeName) => camelize(attributeName), updateTag: (tagName) => { if (tagName === 'placemark') return 'placemarks'; if (tagName === 'timeStep') return 'timesteps'; } }; // 创建输出流,避免内存溢出 const outputStream = fs.createWriteStream('src/forecast/forecast.json'); outputStream.write('{"placemarks": ['); let isFirstEntry = true; const streamParser = new XMLStreamParser(parserOptions); // 监听解析出的节点数据 streamParser.on('data', (data) => { if (data.placemarks) { // 处理JSON格式的逗号分隔 if (!isFirstEntry) outputStream.write(','); else isFirstEntry = false; // 写入单个节点数据 outputStream.write(JSON.stringify(data.placemarks)); } }); // 解析完成后补全JSON结构 streamParser.on('end', () => { outputStream.write(']}'); outputStream.end(); }); // 处理解析错误 streamParser.on('error', (err) => { console.error('解析失败:', err); outputStream.end(); }); // 流式读取大XML文件并喂给解析器 fs.createReadStream('path/to/your/large.xml').pipe(streamParser); // 驼峰转换工具函数 function camelize(str) { return str.replace(/-([a-z])/g, (match) => match[1].toUpperCase()); }
方案二:基于sax手动构建JSON并流式写入
如果需要更精细的节点控制,可以直接用sax监听XML事件,手动构建JSON结构并逐步写入:
import fs from 'fs'; import sax from 'sax'; const outputStream = fs.createWriteStream('src/forecast/forecast.json'); const saxStream = sax.createStream(false, { trim: false, lowercase: false, xmlns: true, position: false }); outputStream.write('{"placemarks": ['); let isFirstPlacemark = true; const parentStack = []; // 监听标签打开事件 saxStream.on('opentag', (node) => { // 处理标签名转换和命名空间移除 let tagName = node.name.replace(/^\w+:/, ''); tagName = tagName === 'placemark' ? 'placemarks' : tagName === 'timeStep' ? 'timesteps' : camelize(tagName); // 构建当前节点对象,处理属性 const currentNode = {}; if (node.attributes) { Object.keys(node.attributes).forEach(attr => { const camelAttr = camelize(attr.replace(/^\w+:/, '')); currentNode[camelAttr] = node.attributes[attr]; }); } // 维护节点层级关系 if (parentStack.length === 0) { parentStack.push(currentNode); } else { const parent = parentStack[parentStack.length - 1]; if (Array.isArray(parent[tagName])) { parent[tagName].push(currentNode); } else if (parent[tagName]) { parent[tagName] = [parent[tagName], currentNode]; } else { parent[tagName] = currentNode; } parentStack.push(currentNode); } }); // 监听文本内容 saxStream.on('text', (text) => { const trimmedText = text.trim(); if (trimmedText && parentStack.length > 1) { const parent = parentStack[parentStack.length - 2]; const currentTag = Object.keys(parent).find(key => parent[key] === parentStack[parentStack.length - 1]); if (currentTag) { // 如果是纯文本节点,直接赋值 if (typeof parent[currentTag] === 'object') { parent[currentTag]._text = trimmedText; } else { parent[currentTag] = trimmedText; } } } }); // 监听标签关闭事件,写入完成的节点 saxStream.on('closetag', (tagName) => { tagName = tagName.replace(/^\w+:/, ''); if (tagName === 'placemark') { parentStack.pop(); const parent = parentStack[parentStack.length - 1]; const lastPlacemark = parent.placemarks.pop(); if (!isFirstPlacemark) outputStream.write(','); outputStream.write(JSON.stringify(lastPlacemark)); isFirstPlacemark = false; } else { parentStack.pop(); } }); // 解析完成补全JSON saxStream.on('end', () => { outputStream.write(']}'); outputStream.end(); }); // 处理错误 saxStream.on('error', (err) => { console.error('解析失败:', err); outputStream.end(); }); // 流式读取XML文件 fs.createReadStream('path/to/your/large.xml').pipe(saxStream); // 驼峰转换工具函数 function camelize(str) { return str.replace(/-([a-z])/g, (match) => match[1].toUpperCase()); }
关键注意事项
- 流式处理时要避免内存泄漏,比如方案二中处理完
placemark节点后及时从父对象中移除,避免积累过多数据。 - 需根据实际XML结构调整节点处理逻辑,比如根节点层级、目标节点的嵌套关系。
- 写入JSON时要严格保证格式正确,避免出现多余逗号或缺失括号导致JSON无效。
内容的提问来源于stack exchange,提问作者gweppi
相关产品推荐
相关产品推荐

