Json.NETでキー名が不定のJSONを読み込む方法

問題が発生したため、プログラムが正しく動作しなくなりました・・・!?

初めての JSON

休日の趣味プログラミングで「デレマスボーダーbot」の「Cinderella API」を利用するアプリを作成したのですが、仕事で使う Web APIのレスポンスは XML形式ばかりで JSON形式の Web APIは初めてだったので色々と試行錯誤した内容を共有したいと思います。

Json.NETを使ってみたが…

仕事で読み込む XMLは情報量が少ないので XDocumentでゴリゴリやってしまうのですが、Cinderella APIは情報量が多くできるだけ楽をしたいので JSONの読み込みには Json.NETを使用することにしました。Json.NETを使用すれば以下の様なコードだけで Cinderella APIの JSONを読み込めると思ったからです。

Json.NETで JSONを読み込む
// デシリアライズ
var producers = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
しかし、Cinderella APIの JSONはこのコードだけでは例外が発生して読み込むことができませんでした。原因は記事タイトルの通りキー名(赤字部分)が不定だったためです。
Cinderella APIの JSON (APIページの ex. より)
// プロデューサー明細情報
{
  // イベント毎に変わるキー名
  "272": { 
  "eventEndDateTime": "2019-04-08T14:00:00Z",
  ~ 中略 ~
  "eventName": "花見DEドリームLIVEフェスティバル"
 },
~ 以下、イベント毎のオブジェクト配列 ~

キー名が不定な場合の読み込み方法

付け焼き刃ながら Json.NETについて調べた結果、次の手順で Cinderella APIの JSONを読み込めるようになりました。今回はプロデューサー明細情報の APIを例に紹介します。

手順1
プロデューサー明細情報のクラスを作成
まずは JSONの内容を格納するプロデューサー明細情報のクラスを作成します。
コード
// プロデューサー明細情報
public class Producer
{
    public int eventDetailId { get; set; }     // イベント明細ID(キー)
    public string eventName { get; set; }      // イベント名
    ~ 中略 ~
    public string favorite3Hash { get; set; }  // ホシイモノ3カードハッシュ
    public string favorite3Name { get; set; }  // ホシイモノ3カード名
}
手順2
プロデューサー明細情報のリストを格納するクラスを作成
次に プロデューサー明細情報のリストを格納するクラスを作成します。JsonConverter属性に指定しているクラスは次のステップで作成します。
コード
// プロデューサー明細情報 モデル
[Newtonsoft.Json.JsonConverter(typeof(ProducersModelConverter))]
class ProducersModel
{
    public List<Producer> Producers { get; set; }
}
手順3
JsonConverterを継承して ReadJsonをオーバーライド
次に手順2で属性に記述した JsonConverterを継承したクラスを作成します。ここで不定なキー名の処理を行います。
コード
// プロデューサー明細情報 コンバーター
public class ProducersModelConverter : Newtonsoft.Json.JsonConverter
{
    public override bool CanConvert(Type objectType) => objectType == typeof(List<Producer>);

    public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        var producers = serializer.Deserialize<JObject>(reader).Properties().Select(p =>
        {
            // 値をオブジェクトに変換
            var producer = p.Value.ToObject<Producer>();
            // 必要に応じて不定なキー名を処理
            producer.eventDetailId = int.Parse(p.Name);

            return producer;
        }).ToList();

        return new ProducersModel() { Producers = producers };
    }
    ~ 以下略 ~
}
手順4
Cinderella APIの JSONを読み込む
これまでに作成したクラスを Json.NETに渡して Cinderella APIの JSONをデシリアライズしてもらいます。
Cinderella APIの JSONを読み込む
// デシリアライズ
var producers = Newtonsoft.Json.JsonConvert.DeserializeObject<ProducersModel>(json);
以上の手順で何とか Cinderella APIのプロデューサー明細情報の JSONを読み込むことができました。Cinderella APIのイベント情報などは更にネストしていてもう少し工夫が必要になりますが基本的な考え方は変わりません。JSON形式を扱うこと自体が初めてなのでこれが最適解かはわかりませんが参考になれば幸いです。

参考にした記事

参考 C# Json.NET 入門3 - 動的なRootNameのJsonをデシリアライズBEACHSIDE BLOG