こんにちは、トミセン(@tomisenblog)です。
C#でCSVの読み込みに便利なCsvHelperですが、
「DateTime型のマッピングってどうやるの?」
「DateTime型のマッピングって、ITypeConverterを継承して自分で書かないとダメなのかな?」
やり方が分からないし、情報も見つからなくて疲れてしまったり、上手くいかなくて思ったより時間がかかってしまっていませんか。
今回はそんなお悩みを解決します。ずばり!
「CsvHelper DateTime型(日付型)に変換してマッピングする方法」についてご紹介します。
それでは一緒に学んでいきましょう!
現場レベルのCsvHelperの実装サンプルを作りました。
CSVデータの取得から、入力チェック、モデル変換まで網羅しています。
5つのシステムで実証したコピペで使い回せる実装法
» 【CsvHelper マスター講座】を見る
DateTime型(日付型)に変換してマッピングする方法
ここからはCsvHelperでDateTime型(日付型)に変換してマッピングするための具体的な方法について解説していきます。サンプルコードを使って解説していきますので、一緒に学習していきましょう。
方法は2つあります!
実は、CsvHelperでDateTime型(日付型)に変換してマッピングするには、2つの方法を使ってできるんです!最初は、TypeConverterOptionでデータを読み込む方法について詳しく解説します。
TypeConverterOptionとは、マッピングの型変換時に使用されるオプションを指定するメソッドです。CSVファイルのデータをnullとして扱う際にも使用されるなど、とても多くの機能をもっています。
CSVファイルのデータをnullとして扱う方法について知りたい方は「CsvHelper マッピングで特定の値をnullとして扱う方法」をどうぞ。
方法①:TypeConverterOptionを使う方法
TypeConverterOptionを使ってマッピングしてみよう!
検証したCsvHelperは、バージョン 15.0.0です。
テストデータ
1,鈴木,20200312,,555
2,佐藤,20200312,20201011,666
ソースコード
public class Test
{
public void Main()
{
List<Member> members = null;
using (var reader = new StreamReader(@"C:csvtest.csv", Encoding.GetEncoding("SHIFT_JIS")))
using (var csv = new CsvHelper.CsvReader(reader, new CultureInfo("ja-JP", false)))
{
csv.Configuration.HasHeaderRecord = false;
csv.Configuration.RegisterClassMap<MemberMapper>();
members = csv.GetRecords<Member>().ToList();
}
// ログ出力
foreach (var member in members)
{
Console.WriteLine($"Seq:{ValueOf(member.Seq)} Name:{ValueOf(member.Name)} Date:{ValueOf(member.Date)} NullableDate:{ValueOf(member.NullableDate)} Amount:{ValueOf(member.Amount)}");
}
}
/// <summary>
/// nullなら文字列"<null>"を返す
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public string ValueOf(object obj)
{
return obj?.ToString() ?? "<null>";
}
}
/// <summary>
/// データ格納用クラス
/// </summary>
public class Member
{
public int Seq { get; set; }
public string Name { get; set; }
public DateTime Date { get; set; }
public DateTime? NullableDate { get; set; }
public decimal Amount { get; set; }
}
/// <summary>
/// マッピング用クラス
/// </summary>
public class MemberMapper : CsvHelper.Configuration.ClassMap<Member>
{
public MemberMapper()
{
Map(x => x.Seq).Index(0);
Map(x => x.Name).Index(1);
Map(x => x.Date).Index(2).TypeConverterOption.Format("yyyyMMdd");
Map(x => x.NullableDate).Index(3).TypeConverterOption.Format("yyyyMMdd");
Map(x => x.Amount).Index(4);
}
}
// 【実行結果】
// Seq:1 Name:鈴木 Date:2020/03/12 0:00:00 NullableDate:<null> Amount:555
// Seq:2 Name:佐藤 Date:2020/03/12 0:00:00 NullableDate:2020/10/11 0:00:00 Amount:666
ソースコードだけでは分かりにくい部分があるので、いくつかのポイントをコードを見ながら解説していきます!
今回は実行結果がどうなったか確認するためにログを出力しています。それを指示しているのが次の場所になります。
// nullなら"<null>"を表示
Console.WriteLine($"Seq:{ValueOf(member.Seq)} Name:{ValueOf(member.Name)} Date:{ValueOf(member.Date)} NullableDate:{ValueOf(member.NullableDate)} Amount:{ValueOf(member.Amount)}");
Nullable<DateTime>型のテストもしたいのでマッピングの結果がnullなら”<null>”と表示されるようにしました。
ログ出力時にValueOf(member.Name)のところでValueOf()メソッドを使って“<null>”を表示するようにしています。
// ログ出力時に文字列を変換するメソッド
public string ValueOf(object obj)
{
// objがnullじゃなければ、ToString()する, nullなら"<null>"を表示する
return obj?.ToString() ?? "<null>";
}
ValueOf()メソッドの内容です。ここでは、ログ出力時に文字列を変換しているところです。
objがnullじゃなければ、ToString()した値を返して、nullなら”<null>”文字列を返すようにしています。
ログ出力の準備出来たら、早速DateTime型(日付型)に変換してマッピングする部分に移ります。マッピング用クラスを使って、処理を作っていきます。
// int型に変換(何も書かない)
Map(x => x.Seq).Index(0);
// string型に変換(何も書かない)
Map(x => x.Name).Index(1);
//"yyyyMMdd"形式で読み込んでDateTimeに変換
Map(x => x.Date).Index(2).TypeConverterOption.Format("yyyyMMdd");
// "yyyyMMdd"形式で読み込んでDateTime?に変換
Map(x => x.NullableDate).Index(3).TypeConverterOption.Format("yyyyMMdd");
// decimal型に変換(何も書かない)
Map(x => x.Amount).Index(4);
.TypeConverterOption.Format(“yyyyMMdd”)で“yyyyMMdd”という形式の日付文字を日付に変換することができます。
DateTime型・DateTime?型、それぞれの場合の結果はどうなっているでしょうか。
次の実行結果をご覧ください。
// 【実行結果】
// Seq:1 Name:鈴木 Date:2020/03/12 0:00:00 NullableDate:<null> Amount:555
// Seq:2 Name:佐藤 Date:2020/03/12 0:00:00 NullableDate:2020/10/11 0:00:00 Amount:666
データフィードが“yyyyMMdd”形式のDateプロパティ・NullableDateプロパティは日付型に変換されています。
また、データフィードが“”で日付型に変換できなかったSeq:1のNullableDateプロパティは<null>に変換されてnullとして扱われていることがわかります。
以上で「DateTime型(日付型)に変換してマッピングする」ことが出来ました!
方法②:FormatAttributeを使う方法
FormatAttributeを使ってマッピングしてみよう!
もう一つの方法はFormatAttributeを使用してマッピングする方法です。
FormatAttributeを使用するとデータ格納用クラスに属性を追加するだけなので、比較的簡単に読み込むことができます。マッピング用クラスを作る必要はありません。
こちらもコードの解説をしていきます。是非マスターしてみてくださいね!
テストデータ
先ほどのと同じデータです。
ソースコード
public class Test
{
public void Main()
{
List<Member> members = null;
using (var reader = new StreamReader(@"C:csvtest.csv", Encoding.GetEncoding("SHIFT_JIS")))
using (var csv = new CsvHelper.CsvReader(reader, new CultureInfo("ja-JP", false)))
{
csv.Configuration.HasHeaderRecord = false;
members = csv.GetRecords<Member>().ToList();
}
// ログ出力
foreach (var member in members)
{
Console.WriteLine($"Seq:{ValueOf(member.Seq)} Name:{ValueOf(member.Name)} Date:{ValueOf(member.Date)} NullableDate:{ValueOf(member.NullableDate)} Amount:{ValueOf(member.Amount)}");
}
}
/// <summary>
/// nullなら文字列"<null>"を返す
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public string ValueOf(object obj)
{
return obj?.ToString() ?? "<null>";
}
}
/// <summary>
/// データ格納用クラス
/// </summary>
public class Member
{
[Index(0)]
public int Seq { get; set; }
[Index(1)]
public string Name { get; set; }
[Index(2)]
[Format("yyyyMMdd")]
public DateTime Date { get; set; }
[Index(3)]
[Format("yyyyMMdd")]
public DateTime? NullableDate { get; set; }
[Index(4)]
public decimal Amount { get; set; }
}
// 【実行結果】
// Seq:1 Name:鈴木 Date:2020/03/12 0:00:00 NullableDate:<null> Amount:555
// Seq:2 Name:佐藤 Date:2020/03/12 0:00:00 NullableDate:2020/10/11 0:00:00 Amount:666
こちらも、いくつかのポイントをコードを見ながら解説していきます!
先ほどと同じところは説明を省きます。
早速DateTime型(日付型)に変換してマッピングする部分を見てみましょう。
こちらではマッピング用クラスがないのでデータ格納用クラスを使って、設定していきます。
// int型に変換(何も書かない)
[Index(0)]
public int Seq { get; set; }
// string型に変換(何も書かない)
[Index(1)]
public string Name { get; set; }
//"yyyyMMdd"形式で読み込んでDateTimeに変換
[Index(2)]
[Format("yyyyMMdd")]
public DateTime Date { get; set; }
// "yyyyMMdd"形式で読み込んでDateTime?に変換
[Index(3)]
[Format("yyyyMMdd")]
public DateTime? NullableDate { get; set; }
// decimal型に変換(何も書かない)
[Index(4)]
public decimal Amount { get; set; }
[Format(“yyyyMMdd”)]と属性を設定することでDateTime型(日付型)に変換してマッピングすることができます。
結果はどうなっているでしょうか。次の実行結果をご覧ください。
// Seq:1 Name:鈴木 Date:2020/03/12 0:00:00 NullableDate:<null> Amount:555
// Seq:2 Name:佐藤 Date:2020/03/12 0:00:00 NullableDate:2020/10/11 0:00:00 Amount:666
結果は先ほどと同じですね。
データフィードが“yyyyMMdd”形式のDateプロパティ・NullableDateプロパティは日付型に変換されています。
また、データフィードが“”で日付型に変換できなかったSeq:1のNullableDateプロパティは<null>に変換されてnullとして扱われていることがわかります。
このやり方でも「DateTime型(日付型)に変換してマッピングする」ことが確認出来ました!
最後に、非常に便利なポイントを一つだけ!
Index属性と、Format属性は1行で書くこともできます。次を見てください。
//"yyyyMMdd"形式で読み込んでDateTimeに変換
[Index(2), Format("yyyyMMdd")]
public DateTime Date { get; set; }
カンマで区切って属性を複数指定することができます。
CsvHelperの属性やAttributesは調べるとけっこういろいろなものがあります。機会を見つけて記事にしていきたいと思います。
まとめ
CsvHelperのマッピングで特定の値をnullとして扱う方法について紹介しました。
マッピングや型変換はCsvHelperでけっこう分かりにくい部分なのかなって感じてます。
Configuration設定や使い方をマスターするとCSVの読み込みがもっと楽になりますよ。
興味のある方は「CsvHelper Configurationの設定」の記事をどうぞ。