Deno Advent Calendar 2020 4日目の記事です🦕
今日は、DenoでCSVを処理する方法について説明します。
はじめに
この記事では、Denoの標準モジュールを使ってCSVを処理する方法について解説します。
Deno及び各ライブラリは以下のバージョンを想定しています。
項目 | バージョン |
---|---|
deno | v1.5.4 |
deno_std | v0.79.0 |
簡単な例
試しに次のようなCSVファイルを処理してみます。
name,age
hoge,20
piyo,30
fuga,40
Denoの標準モジュールには、CSVを処理するためのモジュール(std/encoding/csv
)が存在するので、これを使ってみます。
std/encoding/csv
で提供されているparse
関数を使うことで、CSVデータを解析することができます。
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
関数にReader
interfaceを実装したオブジェクトを渡す必要があります。
Deno.open
で返却されるFile
オブジェクトはReader
interfaceを実装しているため、そのまま渡すことができます。
const buf = BufReader.create(file);
parse
関数を使ってCSVを解析する
あとは、作成したBufReader
をparse
関数に渡すことで、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引数でskipFirstRow
にtrue
を指定します。
すると、CSVの最初の行がスキップされます。
const result = await parse(buf, { skipFirstRow: true });
また、skipFirstRow
にtrue
を設定した際は、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/csv
でstringify
関数が実装されました。(おそらく、Denoのv1.6.0のリリースに合わせて、提供されると思います)
これにより、将来的には、JavaScriptのオブジェクトをCSV形式でファイルに書き出すことも容易になります。
興味があれば、ぜひ使ってみてください!