🛵

【C#】JsonExtensionDataを活用した動的プロパティの取り扱い

2025/03/08に公開

概要

C# の JsonExtensionData 属性を使う機会があったため、その挙動を整理しつつ、サンプルコードを用いて理解を深めていきたいと思います。

JsonExtensionDataとは

JsonExtensionDataは、クラスに定義されていないプロパティを動的に保持するための機能です。これにより、事前にどのプロパティが含まれるかわからない JSON データでも、柔軟に扱うことができます。

JsonExtensionDataの基本的な使い方

実例を見た方がイメージが湧きやすいので、野球選手のデータを例に見ていきます。

野手と投手では見るべき成績が異なります。例えば:

  • 野手: 打率 (BattingAverage), 本塁打 (HomeRuns), 打点 (RBIs) など
  • 投手: 防御率 (ERA), 奪三振数 (Strikeouts), 勝利数 (Wins) など

このように、ポジションごとに異なるプロパティを持つ JSON データを JsonExtensionData を用いて C# クラスにマッピングしてみます。

サンプルコード

まず、選手を表す Player クラスを定義します。

using System.Text.Json;
using System.Text.Json.Serialization;

public class Player
{
    public string Name { get; set; } = default!;
    public string Position { get; set; } = default!;
    public int Number { get; set; }
    public string Team { get; set; } = default!;

    [JsonExtensionData]
    public Dictionary<string, JsonElement> AdditionalProperties { get; set; } = new();
}

この Player クラスでは、Name、Position、Number、Team のような基本的な情報をプロパティとして定義していますが、ポジションによって変化する成績に関するプロパティ (打率や防御率など) は JsonExtensionData を付与した AdditionalProperties に格納する設計になっています。

次に、このクラスにマッピングする JSON データを用意します。以下の JSON では、投手は ERA、Strikeouts、Wins を、野手は BattingAverage、HomeRuns、RBIs を持っていることが分かります。

var playersJson = new List<string>
{
    @"{
        ""Name"": ""今永昇太"",
        ""Position"": ""投手"",
        ""Number"": 18,
        ""Team"": ""シカゴカブス"",
        ""ERA"": 2.80,
        ""Strikeouts"": 150,
        ""Wins"": 10
    }",
    @"{
        ""Name"": ""牧秀悟"",
        ""Position"": ""内野手"",
        ""Number"": 2,
        ""Team"": ""横浜DeNAベイスターズ"",
        ""BattingAverage"": 0.296,
        ""HomeRuns"": 29,
        ""RBIs"": 98
    }",
    @"{
        ""Name"": ""筒香嘉智"",
        ""Position"": ""外野手"",
        ""Number"": 25,
        ""Team"": ""横浜DeNAベイスターズ"",
        ""BattingAverage"": 0.322,
        ""HomeRuns"": 44,
        ""RBIs"": 110
    }"
};

JsonExtensionDataを使ったJSONデシリアライズ

それでは、用意した JSON データを Player クラスにデシリアライズ (JSON文字列 → C# オブジェクト変換) してみましょう。

foreach (var playerJson in playersJson)
{
    // デシリアライズ(JSON文字列 → C#オブジェクト)
    var player = JsonSerializer.Deserialize<Player>(playerJson);
    Console.WriteLine($"🧢 Name: {player.Name}, Position: {player.Position}, Number: {player.Number}, Team: {player.Team}");

    Console.WriteLine("\n📊 成績:(AdditionalPropertiesの中身)");
    foreach (var property in player.AdditionalProperties)
    {
        var stringValue = property.Value.ValueKind switch
        {
            JsonValueKind.String => property.Value.GetString(),
            JsonValueKind.Number => property.Value.GetDouble().ToString(),
            _ => "Unknown Type"
        };

        Console.WriteLine($"{property.Key}: {stringValue}");
    }
}

出力される結果は以下のとおりです。

# Playerクラスで定義されたプロパティ
🧢 Name: 今永昇太, Position: 投手, Number: 18, Team: シカゴカブス
# それ以外のプロパティ
📊 成績:(AdditionalPropertiesの中身)
ERA: 2.8
Strikeouts: 150
Wins: 10

--------------------------
# Playerクラスで定義されたプロパティ
🧢 Name: 牧秀悟, Position: 内野手, Number: 2, Team: 横浜DeNAベイスターズ
# それ以外のプロパティ
📊 成績:(AdditionalPropertiesの中身)
BattingAverage: 0.296
HomeRuns: 29
RBIs: 98

--------------------------
# Playerクラスで定義されたプロパティ
🧢 Name: 筒香嘉智, Position: 外野手, Number: 25, Team: 横浜DeNAベイスターズ
# それ以外のプロパティ
📊 成績:(AdditionalPropertiesの中身)
BattingAverage: 0.322
HomeRuns: 44
RBIs: 110

Player クラスに定義されていないプロパティ (ERA, Strikeouts, Wins など) が AdditionalProperties に格納されていることが確認できます。

JsonExtensionDataを使ったJSONシリアライズ

次に、C# オブジェクトを JSON 文字列に変換 (シリアライズ) してみます。

var options = new JsonSerializerOptions
{
    WriteIndented = true,
    Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};

var player = new Player
{
    Name = "今永昇太",
    Position = "投手",
    Number = 18,
    Team = "シカゴカブス",
    AdditionalProperties = new Dictionary<string, JsonElement>
    {
        { "ERA", JsonDocument.Parse("2.80").RootElement },
        { "Strikeouts", JsonDocument.Parse("150").RootElement },
        { "Wins", JsonDocument.Parse("10").RootElement },
    }
};

string serializedJson = JsonSerializer.Serialize(player, options);
Console.WriteLine("\n📜 シリアライズ結果:");
Console.WriteLine(serializedJson);

シリアライズの結果は以下の通りです。

📜 シリアライズ結果:
{
  "Name": "今永昇太",
  "Position": "投手",
  "Number": 18,
  "Team": "シカゴカブス",
  "ERA": 2.80,
  "Strikeouts": 150,
  "Wins": 10
}

[JsonExtensionData]を属性を付与したAdditionalPropertiesに格納したデータが、通常のプロパティと同じように JSON に展開されていることが分かります。

最後に

今回見たように、JsonExtensionDataを使うことで、クラスに事前に定義されていないプロパティを動的に扱うことができます。

通常の用途では、型の安全性を考慮し、適切なモデルクラスを設計するのが望ましいはずです。今回の設計はあくまでサンプルなので、実際のアプリケーションの設計としては微妙だと思われます。それでも、事前に確定しづらいデータ構造を扱う際にはJsonExtensionDataが役立つのではないでしょうか。

Discussion