如何使用Node.js读取并处理超过2GB的大型JSON文件?
碰到这种大文件读取的问题太常见了——你用的fs.readFile会把整个文件一次性塞进内存,而Node.js默认对单个文件的读取内存限制是2GiB,你的3GB文件直接超了,所以才会抛出ERR_FS_FILE_TOO_LARGE这个错误。
要解决这个问题,核心思路是不要一次性加载整个文件到内存,而是用流式处理逐行/逐个元素解析,或者回到MongoDB直接预处理数据,下面是几种实用的方案:
解决方案
方法1:用JSONStream流式解析(推荐)
JSONStream是专门处理大JSON文件的第三方库,它可以把JSON数组拆成单个元素逐个输出,完全不用加载整个文件到内存。
步骤:
- 首先安装依赖:
npm install jsonstream
- 编写处理代码:
const fs = require('fs'); const JSONStream = require('JSONStream'); // 输入文件路径 const inputPath = '/Users/chx/Downloads/dump/off/products.json'; // 输出转换后的文件路径 const outputPath = '/Users/chx/Downloads/dump/off/transformed_products.json'; const readStream = fs.createReadStream(inputPath, 'utf8'); const writeStream = fs.createWriteStream(outputPath); // 先写入JSON数组的开头 writeStream.write('['); let isFirstItem = true; // 解析JSON数组的每个元素 readStream .pipe(JSONStream.parse('*')) // '*'表示匹配数组里的每个元素 .on('data', (product) => { // 这里实现你的nutrients命名转换逻辑 const transformedProduct = { id: product.id, product_name: product.product_name, nutrients: transformNutrientNames(product.nutrients) }; // 处理JSON数组的逗号分隔(第一个元素前面不加逗号) if (!isFirstItem) { writeStream.write(','); } isFirstItem = false; // 写入转换后的对象 writeStream.write(JSON.stringify(transformedProduct)); }) .on('end', () => { // 写入JSON数组的结尾 writeStream.write(']'); writeStream.end(); console.log('所有数据处理完成!'); }) .on('error', (err) => { console.error('处理过程中出错:', err); }); // 示例转换函数,根据你的数据库规则修改 function transformNutrientNames(nutrients) { const transformed = {}; // 比如把原字段名从 camelCase 转成 snake_case,或者映射到你需要的字段 for (const [oldKey, value] of Object.entries(nutrients)) { // 示例:将"energy"改为"calories","fat"改为"total_fat" switch (oldKey) { case 'energy': transformed.calories = value; break; case 'fat': transformed.total_fat = value; break; case 'saturated-fat': transformed.saturated_fat = value; break; // 其他营养成分字段... default: // 如果没有匹配的规则,保留原字段或自定义处理 transformed[oldKey] = value; } } return transformed; }
方法2:原生Node.js readline手动解析(无需第三方库)
如果不想引入第三方依赖,可以用Node.js内置的readline模块逐行读取,手动处理JSON数组的结构:
const fs = require('fs'); const readline = require('readline'); const inputPath = '/Users/chx/Downloads/dump/off/products.json'; const outputPath = '/Users/chx/Downloads/dump/off/transformed_products.json'; const rl = readline.createInterface({ input: fs.createReadStream(inputPath, 'utf8'), crlfDelay: Infinity // 识别所有换行符 }); const writeStream = fs.createWriteStream(outputPath); writeStream.write('['); let isFirstItem = true; let isInsideArray = false; rl.on('line', (line) => { const trimmedLine = line.trim(); // 跳过JSON数组的开头和结尾标记 if (trimmedLine === '[') { isInsideArray = true; return; } if (trimmedLine === ']') { isInsideArray = false; return; } if (!isInsideArray) return; // 去掉行尾的逗号(MongoDB导出的JSON每行结尾会有逗号) const productStr = trimmedLine.replace(/,$/, ''); try { const product = JSON.parse(productStr); const transformedProduct = { id: product.id, product_name: product.product_name, nutrients: transformNutrientNames(product.nutrients) }; if (!isFirstItem) { writeStream.write(','); } isFirstItem = false; writeStream.write(JSON.stringify(transformedProduct)); } catch (err) { console.error('解析某行出错:', err); } }); rl.on('close', () => { writeStream.write(']'); writeStream.end(); console.log('处理完成!'); }); // 复用上面的transformNutrientNames函数 function transformNutrientNames(nutrients) { // 你的转换逻辑... }
方法3:直接在MongoDB中预处理(最省心)
既然你的数据是从MongoDB导出的,不如直接在数据库里完成nutrients的命名转换,再导出干净的文件,这样完全不用处理大JSON:
在MongoDB Compass中打开products集合,创建以下聚合管道并运行:
[ { $project: { id: 1, product_name: 1, nutrients: { // 这里写你的字段映射规则,比如: calories: "$nutrients.energy", total_fat: "$nutrients.fat", saturated_fat: "$nutrients.saturated-fat", protein: "$nutrients.protein", carbohydrates: "$nutrients.carbohydrates" // 其他需要转换的字段... } } }, { $out: "transformed_products" // 将结果输出到新集合 } ]
运行完成后,新集合transformed_products里就是已经转换好的数据,直接导出JSON即可,MongoDB处理大集合的效率比Node.js高很多。
内容的提问来源于stack exchange,提问作者Ripas55
相关产品推荐
相关产品推荐

