在Node.js中将大型CSV文件转换为JSON/Object

16

我正在尝试完成一项看起来不仅应该非常简单,而且是一个足够常见的任务,以至于应该有直接的软件包可用于完成它。我希望将一个大型CSV文件(从关系数据库表导出)转换为JavaScript对象数组。此外,我还想将其导出到.json文件 fixture。

CSV示例:

a,b,c,d
1,2,3,4
5,6,7,8
...

期望的 JSON:

[
{"a": 1,"b": 2,"c": 3,"d": 4},
{"a": 5,"b": 6,"c": 7,"d": 8},
...
]

我尝试了几个node CSV解析器、流处理库和所谓的CSV转JSON库,但似乎都无法得到我想要的结果,或者只有在文件较小的情况下才有效。我的文件大小近1 GB,包含约40m行(会创建40m个对象)。我预计需要将输入和/或输出进行流处理以避免内存问题。

以下是我尝试过的软件包:

我正在使用Node 0.10.6,希望能够推荐一个简单易用的解决方案。也许自己编写代码是最好的选择,但由于Node在0.10.x中改变了API,因此我不确定从哪里开始使用所有流处理功能。

6个回答

8
请查看 node.js csvtojson 模块,它可用作库、命令行工具或 Web 服务器插件。 https://www.npmjs.org/package/csvtojson。源代码可在以下链接找到:https://github.com/Keyang/node-csvtojson
或者从 NPM 仓库安装:
npm install -g csvtojson

它支持任何大小的csv数据/字段类型/嵌套的JSON等,具有许多功能。
示例:
var Converter=require("csvtojson").core.Converter;

var csvConverter=new Converter({constructResult:false, toArrayString:true}); // The constructResult parameter=false will turn off final result construction in memory for stream feature. toArrayString will stream out a normal JSON array object.

var readStream=require("fs").createReadStream("inputData.csv"); 

var writeStream=require("fs").createWriteStream("outpuData.json");

readStream.pipe(csvConverter).pipe(writeStream);

您也可以将其用作命令行工具:

csvtojson myCSVFile.csv

3

虽然这远非完整的答案,但你可能可以根据https://github.com/dominictarr/event-stream来制定你的解决方案。以下是自readme调整的示例:

    var es = require('event-stream')
    es.pipeline(                         //connect streams together with `pipe`
      process.openStdin(),              //open stdin
      es.split(),                       //split stream to break on newlines
      es.map(function (data, callback) { //turn this async function into a stream
        callback(null
          , JSON.stringify(parseCSVLine(data)))  // deal with one line of CSV data
      }), 
      process.stdout
      )

在此之后,我希望您每行都有一堆字符串化的JSON对象。接下来需要将其转换为数组,可以通过在每行末尾附加,,并在最后一行删除它,然后在文件开头和结尾添加[]来完成。 parseCSVLine函数必须配置为将CSV值分配给正确的对象属性。当传递文件的第一行后,这可以相当容易地完成。
我注意到该库未在0.10上进行测试(至少不是与Travis),请注意。您可以自己从源代码运行npm test

谢谢。我一直在尝试使用event-stream,但当它到达es.map时就会失败。我会继续努力,希望能解决这个问题。 - neverfox
我自己没有使用过event-stream。也许可以查看示例代码的测试? - Myrne Stol
es.split() 可能不能足够分割 CSV 行。根据 http://www.rfc-editor.org/rfc/rfc4180.txt,如果换行符在双引号内部,它可以成为值的一部分。 - Evgeniy Generalov

3
我发现使用csvtojson可以更轻松地读取CSV数据。
以下是代码:

var Converter = require("csvtojson").Converter;
var converter = new Converter({});
converter.fromFile("sample.csv",function(err,result){
  var csvData = JSON.stringify
  ([
    {resultdata : result[0]},
    {resultdata : result[1]},
    {resultdata : result[2]},
    {resultdata : result[3]},
    {resultdata : result[4]}
  ]);
  csvData = JSON.parse(csvData);
  console.log(csvData);
});

或者你可以轻松地这样做:

var Converter = require("csvtojson").Converter;
var converter = new Converter({});
converter.fromFile("sample.csv",function(err,result){ 
  console.log(result);
});

这是第一个代码的结果:

[ { resultdata: 
     { 'Header 1': 'A_1',
       'Header 2': 'B_1',
       'Header 3': 'C_1',
       'Header 4': 'D_1',
       'Header 5': 'E_1' } },
  { resultdata: 
     { 'Header 1': 'A_2',
       'Header 2': 'B_2',
       'Header 3': 'C_2',
       'Header 4': 'D_2',
       'Header 5': 'E_2' } },
  { resultdata: 
     { 'Header 1': 'A_3',
       'Header 2': 'B_3',
       'Header 3': 'C_3',
       'Header 4': 'D_3',
       'Header 5': 'E_3' } },
  { resultdata: 
     { 'Header 1': 'A_4',
       'Header 2': 'B_4',
       'Header 3': 'C_4',
       'Header 4': 'D_4',
       'Header 5': 'E_4' } },
  { resultdata: 
     { 'Header 1': 'A_5',
       'Header 2': 'B_5',
       'Header 3': 'C_5',
       'Header 4': 'D_5',
       'Header 5': 'E_5' } } ]

这段代码的源代码可以在以下链接找到: https://www.npmjs.com/package/csvtojson#installation

希望你已经有了一些想法。


2

您可以使用流来处理大文件。以下是您需要做的事情。这应该可以正常工作。

npm i --save csv2json fs-extra // install the modules

const csv2json = require('csv2json');
const fs = require('fs-extra');

const source = fs.createReadStream(__dirname + '/data.csv');
const output = fs.createWriteStream(__dirname + '/result.json');
 source
   .pipe(csv2json())
   .pipe(output );

1

我建议您自己实现逻辑。Node.js在这些任务方面实际上非常出色。

以下解决方案使用流,因为它们不会使您的内存爆炸。

安装依赖项

npm install through2 split2 --save

代码

import through2 from 'through2'
import split2 from 'split2'

fs.createReadStream('<yourFilePath>')
  // Read line by line
  .pipe(split2())
  // Parse CSV line
  .pipe(parseCSV()) 
  // Process your Records
  .pipe(processRecord()) 

const parseCSV = () => {
  let templateKeys = []
  let parseHeadline = true
  return through2.obj((data, enc, cb) => {
    if (parseHeadline) {
      templateKeys = data
        .toString()
        .split(';')
      parseHeadline = false
      return cb(null, null)
    }
    const entries = data
      .toString()
      .split(';')
    const obj = {}
    templateKeys.forEach((el, index) => {
      obj[el] = entries[index]
    })
    return cb(null, obj)
  })
}

const processRecord = () => {
  return through2.obj(function (data, enc, cb) {
    // Implement your own processing 
    // logic here e.g.:
    MyDB
      .insert(data)
      .then(() => cb())
      .catch(cb)
  })
}

要了解更多关于此主题的信息,请访问Stefan Baumgartner在此主题上的优秀tutorial


0

嗯...有很多解决方案,我会再加一个scramjet

$ npm install --save scramjet

然后

process.stdin.pipe(
    new (require("scramjet").StringStream)("utf-8")
)
    .CSVParse()
    .toJSONArray()
    .pipe(process.stdout)

这将以流式方式精确地产生您所描述的内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接