dunno logs

神山生活四年目

C# で CSV を扱うのに CsvHelper を使う

TextFieldParser

を使って CSV を読み込むのが割りと多いのかなと思います。かくゆう私も C# の仕事をしだしてから自前の CSV ファイル用のライブラリ使ってましたが、読み取りは TextFieldParser を内部で使ってました。ついでに言うと、そのライブラリはまだ .NET 2.0 が仕事のメインだった時代だったので、DataTable を経由して CSV の読み書きを行うようなデザインにしてました。

ただ、今や時代は (とっくにですが、、) 変わり、DataTable よりもオブジェクトでやり取りできないと立ちいかなくなったので、作りなおそうかなーとか思ってた所で、「いやいや、もうあるでしょ」と NuGet 探してたらよく使われてるっぽいのがあったという。

TextFieldParser の問題

単にオブジェクトとやり取りするインターフェースのが欲しかったのもあるのですが、TextFieldParser の問題が一部業務上無視できなくなったのもあります。

  • カラム内に改行を含むデータが入っていた時に改行のみの部分を無視してしまう
  • 空のカラムだけの行を無視する

特に一つ目が問題で、もう少し詳細に書くと

名前,容量,備考
カフェラテ,300,甘い
カフェオレ,250,"某コンビニで購入

かなり甘い"

というCSVがあった場合 (1行目はヘッダ)には最後のデータは以下のような文字列として配列に格納されてしまいます。

某コンビニで購入
かなり甘い

たまにぶつかるこの事象にぶつかった場合には TextFieldParser の Readline を使わなければいけないのですが、ReadFields を使わないと区切り文字での解析がされないので、あまり意味がありません。

参考

CsvHelper

https://github.com/JoshClose/CsvHelper

CSV の読み書きを広くサポートしてくれるライブラリです。オブジェクトとのマッピングはもちろんの事、単純に行のカラムを1つずつ操作していく事も可能です。 ただ、やはりこのライブラリを使うのであれば、決まった形式の CSV があり、そのデータをオブジェクトとして取り出したい場合だと思います。

使い方

http://joshclose.github.io/CsvHelper/

例によって、上記の公式ドキュメントが詳細に記載してくれているので特に書くことは無いのですが、自分が使う手順の感じで紹介していきます。

読み込みとオブジェクトとのマッピング

何はなくともマッピングを定義します。DateTime を Nullable にしているのは、未指定を想定したためです。値型でもここの Id のように必ず指定される事が想定されているなら Nullable にする必要はありません。

class User 
{
    public long Id { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public string Email { get; set; }
    public DateTime? LastLoginDate { get; set; }
}

Mapping は以下のようにすれば、カラムのインデックスと対応付けられます。もちろん、飛ばしたいカラムがあれば指定しなければいいだけです。ここの Map クラスの仕様は最近もガラッと変わった (ちょっと前まではコンストラクタ指定) ので、もし上手くいかなければ公式を眺めてみてください。

私はヘッダ無しの CSV が多いので以下のようにする事が多いのですが、ヘッダ行がある場合には Index 指定無しで ヘッダ行の値とクラスのプロパティ名を合わせるだけでもOKです。

public sealed class CustomClassMap : CsvClassMap<User>
{
    public override void CreateMap()
    {
        Map(m => m.Id).Index(0);
        Map(m => m.LastName).Index(1);
        Map(m => m.FirstName).Index(2);
        Map(m => m.Email).Index(4);
        Map(m => m.LastLoginDate).Index(5);
    }
}

次に CsvReader の生成と Mapping の指定。例として StreamReader を指定していますが、File.OpenText でもOKです。公式ドキュメント通り TextReader が取れれば問題ありません。

var csv = new CsvReader(new StreamReader("ファイルのパス"));
csv.Configuration.RegisterClassMap<CustomClassMap>(); 

ここまでで、Mapping は完了です。実行すると、User のリストがとれてくる事が分かります。

追加のトピック

Mapping 部分では、他に Dafault 値を指定できたり、コンバータを指定する事もできます。私はまだ利用したことが無いのですが、Reference Map という一行の CSV を複数クラスにマッピングする機能もあるようです。
また、Mapping に関しても上記のように非常に単純になるのであれば、AutoMap が指定できます。

書き出し

現在公式ドキュメントには Writing の部分がないようなのでちょうどいいですね。(Github の readme に書かれていた頃はあった気がするのですが。たぶん、書く必要も無かったのかな。)

実際、書き出しは簡単に実行できます。例によって StreamWriter である必要は無いです。もし追記したければ File.AppendText で開くと良いでしょう。

using (var writer = new CsvWriter(new StreamWriter("書き出しファイル名")))
{
    writer.WriteRecords(list);
}

CsvWriter はそもそも Mapping を登録できないので、もしも特別なフォーマットをした上で書き出したいのであれば、別途書き出し用のクラスを用意するか、フィールドの書き出し毎にコンバータを渡す必要があります。

すみません、普通に Mapping 渡せます。出社して自分のコード見て気付きました。。アホ。

writer.onfiguration.RegisterClassMap<CustomClassMap>(); 

CsvReader と同じですね。インスタンスの Configuration 経由で渡すか、生成時に Configuration のインスタンスを渡せます。

Configuration

CsvReader, CsvWriter 共に、ファイルパスと共に Configuration を受け取る事ができます。また、生成後に各インスタンス.Configuration で追加設定をする事もできます。
最後に Configuration の設定項目の中から、たまに使うものや注意するものを列挙しておきます。実際使う時は公式を参照のこと。default 値にも注意です。

  • Register Class Map
  • Unregister Class Map
  • Maps
    • これらは Mapping に関するものです。Register についてはここまでで利用しましたが、Unregist と一覧ができます。
  • Property Binding Flags
    • プロパティとインスタンスフィールドのどちら (or 両方) を対象にするかですね。
  • Has Header Record
    • よく使います。ヘッダ行のある無しを指定できます。これは読み込み、書き出しの両方で使う事が多いです。
  • Will Throw On Missing Field
    • そのままになってしまいますが、Mapping で指定していたカラムが見つからなかった場合に例外投げるかの設定です。
  • Detect Column Count Changes
    • 行と行でカラムの数が異なった場合に例外を投げるかの設定です。Will Throw On Missing Field が指定されていれば、そっちが先に出ます。
  • Comment
  • Allow Comments
    • コメント系
  • Quote No Fields
    • 空フィールドをクォートするか。書き出し時に指定します。
  • Encoding
    • Encoding の指定ですが、これはCount Bytes の時に使われるもので、CSV の読み書きには、渡される Reader, Writer の Stream のエンコーディングが使われます。
  • Reading Exception Callback
    • 行の読み込みに失敗した時の callback です。
  • Auto Map
    • 既出ですが、基本的な Mapping を自動で作ります。

まとめ

Csv 関連のライブラリは NuGet を眺めるだけでもたくさんありますし、おそらく仕事の中で一番使い安物を使うのが一番だと思うのですが、今後新規に Csv を扱うコードを書く場合には、 CsvHelper は結構オススメです。
Csv 読み込みは特にテストデータを流し込む時とかには未だによく使うので (yaml でとかカッコいい事できない。。)、その辺りでも活躍してくれています。