Node.jsでcsv-parseを使ってcsvファイルを読み込む

この記事に書いてあること

タイトルの通り、Node.jsでcsv-parseを使ってcsvファイルを読み込む実装を紹介します。 Node.jsにはcsvファイルを読み込むライブラリが複数ありますが、おそらく一番人気っぽいcsv-parseを使ってみました。

csv vs csv-parse vs csv-parser vs fast-csv | npm trends

ちゃんとTypeScriptにも対応しています。

この記事での実装の全体はGitHubに置きました。 よければ参照してください。

実装

使いそうな3つの実装を紹介します。

  • 一括でcsvファイルを読み込む
  • 1行ずつcsvファイルを読み込む
  • S-JIS、空行あり、区切り文字のあと(値の左側)に空白ありのcsvファイルを読み込む(iconv-liteを利用します)

一括でcsvファイルを読み込む

ここで読み込むcsvファイルは以下のような内容です。

id,createdAt,family_name,ニックネーム
abcde12345,2024-01-25T12:34:56+09:00,佐藤,a.sato
fghij12345,2024-02-20T12:34:56+09:00,スズキ,b.suzuki
klmno12345,2023-12-31T04:12:55Z,たかはし,c.takahashi

実装は以下のようになります。

const fs = require('node:fs');
const { parse } = require('csv-parse/sync');

const input = fs.readFileSync('./test.csv');
const records = parse(input, {
  delimiter: ',',
  columns: true,
});

console.log(records);

fsを使ってcsvファイルを読み込み、その読み込み内容をcsv-parseに渡します。 csv-parseのオプションとしては、delimiterプロパティで区切り文字をカンマに指定し、columnsプロパティをtrueにしてcsvファイルにヘッダがあることを指定します。

コンソールの出力結果は以下のように配列になり、配列の要素はヘッダをキーとしたオフジェクトにしてくれます。

ちなみに、ヘッダがない場合には、配列要素も配列になります。

[
  {
    id: 'abcde12345',
    createdAt: '2024-01-25T12:34:56+09:00',
    family_name: '佐藤',
    'ニックネーム': 'a.sato'
  },
  {
    id: 'fghij12345',
    createdAt: '2024-02-20T12:34:56+09:00',
    family_name: 'スズキ',
    'ニックネーム': 'b.suzuki'
  },
  {
    id: 'klmno12345',
    createdAt: '2023-12-31T04:12:55Z',
    family_name: 'たかはし',
    'ニックネーム': 'c.takahashi'
  }
]

1行ずつcsvファイルを読み込む

csvファイルが大きい場合などは一括で読み込まず、1行ずつ読み込むほうが安全です。 以下のようにすると、1行ずつ同期で読み込んで処理をおこなうことができます。

for await...ofをちゃんと使ったことがなかったので、勉強になりました。

for await...of - JavaScript | MDN

const fs = require('node:fs');
const { parse } = require('csv-parse');

(async () => {
  const parser = fs.createReadStream('./test.csv').pipe(parse({
    delimiter: ',',
    columns: true,
  }));

  const records = [];
  for await (const record of parser) {
    records.push(record);
  }

  console.log(records);
})();

コンソールへの出力結果は前の実装と同じなので省略します。

S-JIS、空行あり、区切り文字のあと(値の左側)に空白ありのcsvファイルを読み込む

実際にありそうで、ちょっと困りそうなオプションを諸々詰め込んだcsvファイルを読み込むためのcsv-parseのオプションの使い方です。

今回読み込むCSVは以下のようになっています。 文字コードはS-JISで、4行目と最終行である6行目が空行で、区切り文字であるカンマのあと(値の左側)に半角スペースが1~2つ入っています。

id, createdAt, family_name, ニックネーム
abcde12345, 2024-01-25T12:34:56+09:00, 佐藤, a.sato
fghij12345,  2024-02-20T12:34:56+09:00, スズキ, b.suzuki

klmno12345, 2023-12-31T04:12:55Z, たかはし, c.takahashi

このcsvファイルを読み込むには、まずiconv-liteを使ってS-JISをUTF8に変換します。 そしてcsv-parseのオプションとして、 ltrimプロパティをtrueにして値の左側の半角スペースをトリムし、 skip_empty_linesプロパティをtrueとして空行を除外し、 encodingプロパティで明示的にutf8を指定(デフォルトでもutf8ですが)します。

skip_empty_linesプロパティを使わない場合(デフォルトはfalse)、空行があるとエラーになってしまいます。

const fs = require('node:fs');
const iconv = require('iconv-lite');
const { parse } = require('csv-parse');

const parser = fs.createReadStream('./test2.csv')
  .pipe(iconv.decodeStream('sjis'))
  .pipe(iconv.encodeStream('utf8'))
  .pipe(parse({
    delimiter: ',',
    columns: true,
    ltrim: true,
    skip_empty_lines: true,
    encoding: 'utf8',
  }));

csv-parseには他にもオプションがたくさんあります。 ちゃんとドキュメントになっているので、他に困ったらご確認ください。

CSV Parse - Options

おわりに

csv-parseを使ってCSVファイルを読み込んでみました。

1行ずつ読み込むときに使った、for await...ofをちゃんと使えるようにしておきたいです。

参考