LoginSignup
6
1

More than 3 years have passed since last update.

DenoでCSVを処理する

Posted at

Deno Advent Calendar 2020 4日目の記事です🦕

今日は、DenoでCSVを処理する方法について説明します。

はじめに

この記事では、Denoの標準モジュールを使ってCSVを処理する方法について解説します。
Deno及び各ライブラリは以下のバージョンを想定しています。

項目 バージョン
deno v1.5.4
deno_std v0.79.0

簡単な例

試しに次のようなCSVファイルを処理してみます。

data.csv
name,age
hoge,20
piyo,30
fuga,40

Denoの標準モジュールには、CSVを処理するためのモジュール(std/encoding/csv)が存在するので、これを使ってみます。

std/encoding/csvで提供されているparse関数を使うことで、CSVデータを解析することができます。

sample.ts
import { parse } from "https://deno.land/std@0.79.0/encoding/csv.ts";
import { BufReader } from "https://deno.land/std@0.79.0/io/bufio.ts";

const file = await Deno.open("data.csv");
try {
  const buf = BufReader.create(file);
  const result = await parse(buf);
  console.log(result);
} finally {
  file.close();
}

試しにこのファイル(sample.ts)を実行してみましょう。CSVファイルを読み込むため、--allow-readオプションを指定する必要があります。

$ deno run --allow-read sample.ts

すると、次のように出力されます。

[ [ "name", "age" ], [ "hoge", "20" ], [ "piyo"
, "30" ], [ "fuga", "40" ] ]

解説

CSVファイルを開く

まず、parse関数によってCSVファイルを処理するためには、そのファイルを開く必要があります。Denoでファイルを開くときはDeno.open関数を使います。

const file = await Deno.open("data.csv");

BufReaderを作成する

parse関数は入力元のCSVデータをstringまたはBufReader(std/io/bufioで提供されています)のいずれかの形式で受け取ります。
今回のケースでは、ファイルに格納されたCSVデータを処理したいため、BufReaderの方を使いましょう。

BufReaderを作成する際は、BufReader.create関数にReaderinterfaceを実装したオブジェクトを渡す必要があります。
Deno.openで返却されるFileオブジェクトはReaderinterfaceを実装しているため、そのまま渡すことができます。

  const buf = BufReader.create(file);

parse関数を使ってCSVを解析する

あとは、作成したBufReaderparse関数に渡すことで、CSVデータを解析できます。

  const result = await parse(buf);

parse関数は、デフォルトでは、次のような入れ子の配列を返却します。

[ [ "name", "age" ], [ "hoge", "20" ], [ "piyo"
, "30" ], [ "fuga", "40" ] ]

そのため、for-ofループなどを使って解析結果を処理できます。

  const result = await parse(buf) as [string, number][];
  for (const [name, age] of result) {
    console.log(name, age);
  }

応用編

CSVの最初の行をスキップしたい

次のように、CSVの1行目がヘッダーとして定義されていたとします。

name,age
hoge,20
piyo,30
fuga,40

このような場合、parse関数の第2引数でskipFirstRowtrueを指定します。
すると、CSVの最初の行がスキップされます。

  const result = await parse(buf, { skipFirstRow: true });

また、skipFirstRowtrueを設定した際は、parse関数はオブジェクトの配列を返却します。

[
  { name: "hoge", age: "20" },
  { name: "piyo", age: "30" },
  { name: "fuga", age: "40" }
]

ヘッダーが定義されていないCSVデータを処理したい

例えば、次のようにヘッダーが存在しないCSVデータがあったとします。
(一列目がID、二列目が生徒名、三列目がテストの得点というようなデータを想定しています)

1,taro,100
2,jiro,68
3,hanako,45

このようなヘッダーが存在しないデータを処理することを想定し、parse関数はcolumnsオプションを提供しています。

例えば、次のようにcolumnsオプションを指定して実行してみます。

  const result = await parse(buf, {
    columns: [
      { name: "id", parse: Number }, 
      { name: "student" },
      {
        name: "rank",
        parse: _score => {
          const score = Number(_score);
          if (score === 100) {
            return "A";
          } else if (score >= 80) {
            return "B";
          } else if (score >= 60) {
            return "C";
          } else if (score >= 40) {
            return "D";
          } else {
            return "E";
          }
        }
      },
    ]
  });

すると、parse関数は、次のようなオブジェクトの配列を返却します。

[
  { id: 1, student: "taro", rank: "A" },
  { id: 2, student: "jiro", rank: "C" },
  { id: 3, student: "hanako", rank: "D" }
]

TSV形式のデータを処理したい

次のように、カンマではなくタブで区切られたデータがあったとします。

id  name    age
1   hoge    15
2   piyo    24
3   fuga    32

このような場合は、separatorオプションを指定すると、適切に処理できます。

  const result = await parse(buf, {
    separator: "\t",
    skipFirstRow: true,
  });
  // [
  //   { id: "1", name: "hoge", age: "15" },
  //   { id: "2", name: "piyo", age: "24" },
  //   { id: "3", name: "fuga", age: "32" }
  // ]

終わりに

この記事では、DenoでCSVを処理する方法について解説しました。

最近、std/encoding/csvstringify関数が実装されました。(おそらく、Denoのv1.6.0のリリースに合わせて、提供されると思います)
これにより、将来的には、JavaScriptのオブジェクトをCSV形式でファイルに書き出すことも容易になります。

興味があれば、ぜひ使ってみてください!

参考

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1