C#のNewtonsoft.Jsonで基底クラスのプロパティをJSONの値によってインスタンスを切り替える方法

2024年12月11日水曜日

C#

t f B! P L

C#でJSON形式の文字列をクラスのインスタンスに変換する際、基底クラスのプロパティに格納される具体的な派生クラスの型をJSONの値に基づいて切り替えたい場合があります。このようなシナリオでは、Newtonsoft.Jsonライブラリを使用してJsonConverterをカスタマイズする方法があります。この記事では、この実装方法について詳しく解説します。

具体的なユースケース

以下のようなクラス構造を考えます。

public abstract class Animal
{
    public string Type { get; set; }
    public string Name { get; set; }
}

public class Dog : Animal
{
    public string Breed { get; set; }
}

public class Cat : Animal
{
    public bool LikesMilk { get; set; }
}

public class Zoo
{
    public Animal Animal { get; set; }
}

JSONのデータは以下のような形式を想定します。

{
    "Animal": {
        "Type": "Dog",
        "Name": "Buddy",
        "Breed": "Golden Retriever"
    }
}

このTypeフィールドに基づいて、AnimalプロパティのインスタンスをDogまたはCatとしてデシリアライズしたい場合があります。

JsonConverterのカスタマイズ

まず、基底クラスAnimalに対応するカスタムJsonConverterを実装します。

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;

[JsonConverter(typeof(AnimalConverter))]
public abstract class Animal { }

public class AnimalConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(Animal).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
//        if (reader.TokenType == JsonToken.Null)
//        {
//            return null;
//        }

        JObject jObject = JObject.Load(reader);
        string type = jObject["Type"]?.ToString();

        Animal animal;
        switch (type)
        {
            case "Dog":
                animal = new Dog();
                break;
            case "Cat":
                animal = new Cat();
                break;
            default:
                throw new InvalidOperationException($"Unknown animal type: {type}");
        }

        serializer.Populate(jObject.CreateReader(), animal);
        return animal;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException("Serialization is not implemented");
    }
}

public class Dog : Animal
{
    public string Name { get; set; }
    public string Breed { get; set; }
}

public class Cat : Animal
{
    public string Name { get; set; }
    public bool LikesMilk { get; set; }
}

基底クラスのプロパティにコンバーターを適用

アノテーションを使用することで、Animalプロパティに対するカスタムコンバーターを簡単に設定できます。

using Newtonsoft.Json;

public class Zoo
{
    [JsonConverter(typeof(AnimalConverter))]
    public Animal Animal { get; set; }
}

これにより、Zooクラスのデシリアライズ時にAnimalConverterが自動的に適用されます。

使用例

以下は、デシリアライズのコード例です。

using Newtonsoft.Json;

public class Program
{
    public static void Main(string[] args)
    {
        string json = @"{
            \"Animal\": {
                \"Type\": \"Dog\",
                \"Name\": \"Buddy\",
                \"Breed\": \"Golden Retriever\"
            }
        }";

        Zoo zoo = JsonConvert.DeserializeObject<Zoo>(json);

        Console.WriteLine($"Animal Name: {zoo.Animal.Name}");
        if (zoo.Animal is Dog dog)
        {
            Console.WriteLine($"Breed: {dog.Breed}");
        }
    }
}

実行結果

上記のコードを実行すると、次のような出力が得られます。

Animal Name: Buddy
Breed: Golden Retriever

まとめ

このように、Newtonsoft.JsonのカスタムJsonConverterを使用することで、JSONの値に応じて基底クラスのプロパティのインスタンスを動的に切り替えることが可能です。

スポンサーリンク
スポンサーリンク

このブログを検索

Profile

自分の写真
Webアプリエンジニア。 日々新しい技術を追い求めてブログでアウトプットしています。
プロフィール画像は、猫村ゆゆこ様に書いてもらいました。

仕事募集もしていたり、していなかったり。

QooQ