ほげほげー

C#メインにプログラミング周りから日常のあれこれとかを不定期に書いていきます。

System.Text.Json.JsonSerializer のジェネリック版関数について

.NET Core 3.1 から JsonSerializer が入ったので今更ながら試してみようかと思った触ってみた時に遭遇した、ちょっと考えればまぁ分からないでもないけど罠だなぁ…っていう挙動を覚書。

JsonSerializer について

JsonSerializer は .NET Core 3.1 から導入されたシリアライザです。

JsonSerializer にはジェネリック版と非ジェネリック版の Serialize, Deserialize が用意されています。

基本的にはジェネリック版で良いと思いますが、ポリモーフィズムを持ったオブジェクトを扱う際には注意が必要になります。

ちなみにポリモーフィズムを持ったオブジェクトのデシリアライズにはデフォルトでは対応していないので、JsonConverter<T> を継承して独自実装する必要があります。 これは後日。

実際に困る場合の例はこちら。

public class Program
{
    public abstract class Item
    {
        public int Price { get; init; }
        public string Name { get; init; }
    }

    public class Fruit : Item
    {
        public int SugarContent { get; init; }
    }

    public class Meat : Food
    {
        public int FatLevel { get; init; }
    }

    public Main()
    {
    Item item = new Fruit
        {
            Price = 100,
            Name = "りんご",
            SugarContent = 14
        };
        var json = JsonSerializer.Serialize(item);
        // 以下略
    }
}

こうした時に、json の中身には Item が持っているプロパティしか存在せず、情報が欠落してしまいます。

Item 型の変数に入れた状態で Serialize したため <T> が Item になった結果です。

これを回避するためには

  1. 具体的な型の変数に入れる
  2. ジェネリック版の関数を使って Serialize(item, item.GetType()) とする
  3. JsonConverter<Item> を実装して派生クラスの扱いを記述する

の3つが選択肢に上がります。

現実的にはには Item の配列とかが持ちたいケースが大半だったりすると思うので、JsonConverter 一択になるんじゃないかとは思います。

JsonConverter の実装が面倒な場合はシリアライズ、デシリアライズ時に具体的な型を指定するような構造にすることが必要になります。その場合は非ジェネリック版の関数を必ず使う、とかしないと事故るケースが出てきそうです。*1

*1:とはいえ、そんなことは書き捨てのコード以外ではやらないと思いますが