かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

CsvHelper でパースするときに NULL という文字列のセルを null として扱いたい

こんな csv を扱う必要がありまして。

Tanaka,38
Kazuki,NULL
Kazuakix,98
muu,NULL

null が入ってるところには丁寧に文字列で NULL と記載されています。 こういう csv を見るたびにデータ部分に NULL っていう文字列データつっこんだらどうするんだろうと思ってしまう今日この頃です。 今回の例は、数字の列なので、そういういたずらは出来ませんが。

ということで個人的に C# で CSV を扱う時によくつかってる CsvHelper でのやりかたです。

ドキュメント的にはここらへんですね。

joshclose.github.io

joshclose.github.io

ClassMap というクラスを使ってクラスと csv のマッピングをカスタマイズできるのですが、ここで TypeConverter というのをかませることが出来ます。 ということで、今回の場合はこういう TypeConverter を作って…。

class NullableIntConverter : ITypeConverter
{
    public object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
    {
        return text == "NULL" ?
            null :
            (int?)int.Parse(text); // 数字じゃないデータきたら潔く死ぬ
    }

    public string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData)
    {
        throw new NotImplementedException(); // 今回は読込だけなので、こっちは実装しない。
    }
}

そして、こんな感じにクラスと ClassMap を作ります。

class Record
{
    public string Name { get; set; }
    public int? Age { get; set; }
}

class RecordMap : ClassMap<Record>
{
    public RecordMap()
    {
        this.AutoMap();
        this.Map(x => x.Age).TypeConverter<NullableIntConverter>();
    }
}

では、さっそく試してみましょう。

namespace CSVHelperLab
{
    class Program
    {
        static void Main(string[] args)
        {
            var csv = @"Tanaka,38
Kazuki,NULL
Kazuakix,98
muu,NULL";
            using (var p = new CsvReader(new StringReader(csv)))
            {
                p.Configuration.HasHeaderRecord = false;
                p.Configuration.RegisterClassMap<RecordMap>();
                var records = p.GetRecords<Record>();
                foreach (var record in records)
                {
                    Console.WriteLine($"{record.Name}: {record.Age}");
                }
            }
        }
    }
}

実行するとこうなります。

Tanaka: 38
Kazuki:
Kazuakix: 98
muu:

いい感じですね。