sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

C# System.Text.Json コンバーターを利用する方法

C# .NetJson 形式で書かれたファイルを扱う際は、2022 年現在だと System.Text.Json を利用するのがもっとも手軽だし、一般的だと思います。

このあたりと基本的な使い方については、以下の記事で記述しています。

今回は System.Text.Json でインターフェースを利用したプロパティをデシリアライズするためにコンバーターを利用する方法をメモしました。

インターフェースをデシリアライズする

public void Sample1()
{
    ISample iSample = new Sample(1, 2, "test");

    var options = new JsonSerializerOptions
    {
        WriteIndented = true,
        Converters =
        {
            // まだ未設定
        }
    };

    // シリアライズは型でエラーにならない
    var jsonText = JsonSerializer.Serialize(iSample, options);

    // デシリアライズはインターフェースを解決できないため、エラーになる
    var dSample = JsonSerializer.Deserialize<ISample>(jsonText, options);
}

System.NotSupportedException: 'Deserialization of interface types is not supported.

インターフェース型はサポートしていないという例外が出力されました。しかし、ここで変換をサポートするコンバーターを追加してやれば、変換できるようになります。

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

public class InterfaceConverter<TInterface, TImplement> : JsonConverter<TInterface> where TImplement : TInterface
{
    public override TInterface? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return (TInterface?)JsonSerializer.Deserialize(ref reader, typeof(TImplement), options);
    }

    public override void Write(Utf8JsonWriter writer, TInterface value, JsonSerializerOptions options)
    {
        JsonSerializer.Serialize(writer, value, typeof(TImplement), options);
    }
}

TInterfaceTImplement 型のクラスに変換するコンバーターです。利用したコードは以下のとおり:

public void Sample2()
{
    ISample iSample = new Sample(3, 4, "test2");

    var options = new JsonSerializerOptions
    {
        WriteIndented = true,
        Converters =
        {
            new InterfaceConverter<ISample, Sample>()
        }
    };

    var jsonText = JsonSerializer.Serialize(iSample, options);
    var dSample = JsonSerializer.Deserialize<ISample>(jsonText, options);
}

シリアライズの型を明示しなくても、デシリアライズできるようになりました。

クラスのパラメーターにジェネリック型を含む

以下のクラスの場合はどうでしょうか。

internal class SampleGenerics<T> where T : struct
{
    public T Value1 { get; set; }
    public T Value2 { get; set; }

    public SampleGenerics(T value1, T value2)
    {
        Value1 = value1;
        Value2 = value2;
    }
}

この場合は特に、コンバーターは必要ありません。デシリアライズをするときに型を指定しているためですね。(例のように intdouble に変換することもできます)

public void Sample3()
{
    var sample = new SampleGenerics<int>(5, 6);

    var options = new JsonSerializerOptions
    {
        WriteIndented = true,
        Converters =
        {
        }
    };

    var jsonText = JsonSerializer.Serialize(sample, options);
    var dSample = JsonSerializer.Deserialize<SampleGenerics<double>>(jsonText, options);
}

なので、コンバーターでインターフェースをデシリアライズするケースは、そんなに多くないと思います。むしろ、ちょっと特殊なケースが殆どだと思います。

シリアライズで指定する型だけでは処理しきれなくなったとき、コンバーターの利用すれば丁度よいときがあるので検討しよう。

サンプル

参考