C#
CSV
CSVHelper

CSVHelperで読込時に正規表現チェック

More than 1 year has passed since last update.

最終更新日:2015/12/28

前置き

CSVファイルをCSVHelperで読む時に型変換を行う場合、
Mapする際に [TypeConverter]メソッドをはさむ事で
メジャーな型やNullable、Enumなどに変換して取得する事が出来る。

ただ、郵便番号やメールアドレスのような形式が決まっている文字列も
型としては“String”であり、[TypeConverter] に「郵便番号型」なんてのは存在しない為(当たり前)、
こっち側でデータの中を読みつつ正しいかをチェックしてやる必要がある。
せっかくModelとMapを整えても、結局1項目ずつ読み取るのは何だか勿体無い。

目標

Mapする際に正規表現でパターンマッチングを行い、
パターンにマッチしないデータが存在した時は、他の型のコンバートエラー同様
[CsvTypeConverterException]を返してあげる。

対応方法

今回は「郵便番号(/\A[0-9]{3}-[0-9]{4}\z/)」の正規表現を例に
サンプルソースを書いてみます。

Custom Type Converter を作成する方法

RegexConverter.cs
public class RegexConverter : DefaultTypeConverter
{
  private Regex _regex;
  public RegexConverter(Regex reg) 
  {
    this._regex = reg;
  }

  public override object ConvertFromString(TypeConverterOptions options, string text)
  {
    if (text == null || !this._regex.IsMatch(text))
    {
      return base.ConvertFromString(options, text);
    }
    return text;
  }

  public override bool CanConvertFrom(Type type)
  {
    return type == typeof(string);
  }
}
}
ZipModelMap.cs
public class ZipModelMap : CsvClassMap<ZipModel>
{
  public ZipModelMap()
  {
    Regex zipReg = new Regex(@"\A[0-9]{3}-[0-9]{4}\z");
    Map(x => x.ZipCD).Name("ZipCD").TypeConverter(new RegexConverter(zipReg));
  }
}

ConvertUsing メソッドで無名関数を使用する方法

ZipModelMap.cs
public class ZipModelMap : CsvClassMap<ZipModel>
{
  public ZipModelMap()
  {
    Regex zipReg = new Regex(@"\A[0-9]{3}-[0-9]{4}\z");
    Map(x => x.ZipCD).ConvertUsing<string>(row =>
      {
        string val = row.GetField<string>("ZipCD");
        if (val == null || !zipReg.IsMatch(val))
        {
          thorw new CsvTypeConverterException("The conversion cannot be performed.");
        }
        return val;
      });
  }
}

まとめ

上記両パターン共に、指定した正規表現パターンに当てはまらないデータ存在時
[CsvTypeConverterException]を発生させてやることが出来た。
(タイプミス等のコンパイルエラーに関しては保障出来ないが・・・)

単なる文字列でも、指定した正規表現パターンにコンバート出来なかったら
型変換例外っていうのはちょっとズルいかな?

[Custom Type Converter]と[ConvertUsing]メソッドの使い分けとしては、
正規表現が連発するようなプロジェクトならクラス化しちゃって、
ちょこっと使いたい程度なら無名関数かな?っていう雰囲気で(
要するにただの決めです。どちらでも出来るのでお好きにどうぞ。

補足

[CsvTypeConverterException]は[CsvHelperException]を継承しており、
[CsvHelperException]はExceptionのData["CsvHelper"]に
エラーが発生した際の内容を格納している。

Row: '3' (1 based)
Type: 'CsvHelperSample.Model.ZipModel'
Field Index: '0' (0 based)
Field Name: 'ZipCD'
Field Value: '110001'

こいつと一緒にExceptionの内容を吐き出してやれば、
[GetRecodes<T>]メソッドで取得した時でも、どこでエラーが出たを簡単に取れる。

課題

[Configuration.IgnoreReadingExceptions]と掛け合わせれば
エラー全件取れるかは要考察。

参考サイト

CsvHelper
Custom TypeConverter · JoshClose/CsvHelper Wiki · GitHub