最終更新日: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 を作成する方法
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);
}
}
}
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 メソッドで無名関数を使用する方法
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