8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C#】複数の型が混ざったリストをJsonSerializer.Serialize/Deserializeする

Last updated at Posted at 2025-08-20

前提

自分でJsonを書き出し、そしてそれを読み込むような場面でしか本稿のプログラムは動きません。

クラス定義

以下のようなクラスがあるとします。

// 継承元の抽象クラス(インターフェイスでも可)
abstract class ConfigBase
{
    public string Name { get; set; } = "";
}

// 抽象クラスやインターフェイスから派生したクラス1
class ConfigType1 : ConfigBase
{
    public int Count { get; set; } = 0;
}

// 抽象クラスやインターフェイスから派生したクラス2
class ConfigType2 : ConfigBase
{
    public string Message { get; set; } = "";
}

List<ConfigBase>という型のリストを用意すればConfigType1ConfigType2も、両方リストに放り込むことができますね。

var configs = new List<ConfigBase>();
configs.Add(new ConfigType1 { Name = "hoge", Count = 10});
configs.Add(new ConfigType2 { Name = "fuga", Message="piyo"});

そのままSerialize/Deserializeすると

これをJsonとして読み書きしたいとき、Serialize/Deserializeは自分の理想通りには動いてくれません。

まずはSerializeから。

Serialize
using System.Text.Json;

var jsonText = JsonSerializer.Serialize(configs);

こんな風になってほしいところですが…

Serialize理想
[
  {
    "Name":"hoge",
    "Count": 10
  },
  {
    "Name":"fuga",
    "Message":"piyo"
  }
]

ConfigBaseが持つプロパティのみシリアライズされます。

Serialize現実
[
  {
    "Name":"hoge"
  },
  {
    "Name":"fuga"
  }
]    

Deserializeはというと…

Deserialize
using System.Text.Json;

var configs = JsonSerializer.Deserialize<List<ConfigBase>>(jsonText);

インターフェイスや抽象クラスのDeserializeはサポートされてませんというエラーになります。

Deserialize現実
System.NotSupportedException:
    Deserialization of interface or abstract types is not supported.

そりゃそうだ。そのインターフェイスや抽象クラスがどんな型に派生しているかの知識を持っていないと型変換できないし、まったく同じプロパティをもつ別の派生型があったらどちらの型に変換したらいいか判別できません。

解決策

そのインターフェイスや抽象クラスがどんな型に派生しているかの知識を持っていないと型変換できないし、

💡じゃあ知識を与えればいいんじゃない?

まったく同じプロパティをもつ別の派生型があったらどちらの型に変換したらいいか判別できません。

💡じゃあ元がなんの型だったかメモしておけばいいんじゃない?

JsonDerivedType属性

そのまんま思いついた解決策をやってくれる機能があります。

継承元のクラスにJsonDerivedType属性を追記します。

+ using System.Text.Json.Serialization;

+ [JsonDerivedType(typeof(ConfigType1), typeDiscriminator: nameof(ConfigType1))]
+ [JsonDerivedType(typeof(ConfigType2), typeDiscriminator: nameof(ConfigType2))]
  // 継承元の抽象クラス(インターフェイスでも可)
  abstract class ConfigBase
  {
      public string Name { get; set; } = "";
  }

JsonDerivedType属性でどんな型に派生しているかを教えてあげます。
typeDiscriminator引数についてはSerializeの実行結果を見るとわかります。

Serialize

属性を付けた状態でSerializeを実行すると以下のようになります。

Serialize結果
[
  {
    "$type": "ConfigType1",
    "Count": 10,
    "Name": "hoge"
  },
  {
    "$type": "ConfigType2",
    "Message": "piyo",
    "Name": "fuga"
  }
]

理想の形になっています。
しかし、想定していた出力のほかに$typeというキーが追加されています。
これはJsonにする前はどんな型だったのかというメタデータで、この時の値としてtypeDiscriminatorで指定した値が使用されます。ここでは型名を使用しましたが、それぞれの型を区別さえできれば値はなんでもいいです。

Deserialize

出力されたJsonに元がどんな型だったかが記録されたので、Deserializeもすんなり実行されます。

    var configsResult = JsonSerializer.Deserialize<List<ConfigBase>>(jsonText);
    foreach (var config in configsResult)
    {
        Console.WriteLine(config);
    }
Deserialize結果
ConfigType1 { Name = hoge, Count = 10 }
ConfigType2 { Name = fuga, Message = piyo }

おわりに

無事、多相型リストをSerialize/Deserializeすることができました。
しかし、お分かりのとおり、自分で型情報を出力したJsonならDeserializeできますが、外部サービス等から取得してきたJsonテキストには無力ですので、また別の方法を試す必要があります。

参考にしたページ

8
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?