かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

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:

いい感じですね。