You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js中1.8GB大XML文件转JSON的流式解析问题

大XML文件流式转JSON解决方案

原代码问题分析

  1. XMLParser不支持流式解析:fast-xml-parser的XMLParser是一次性解析完整XML的实例,每次调用parse都会重置内部状态,分块喂数据会导致内部状态混乱,触发addChild相关的错误。
  2. 文件写入逻辑错误:fs.writeFileSync每次都会覆盖文件内容,所以最终只会保留第一块解析结果。
  3. 不完整节点处理失效: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 23:02:51