search
LoginSignup
1

More than 1 year has passed since last update.

posted at

DenoでCSVを処理する

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形式でファイルに書き出すことも容易になります。

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

参考

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
What you can do with signing up
1