🔄

【C#】System.Text.Json と Newtonsoft.Json のデシリアライズ時のJSONの値の型の扱いについて

2023/10/13に公開

Newtonsoft.Json を使っていたプロジェクトを、System.Text.Json に書き換えているときに見つけた挙動の差についてのメモです。

デシリアライズするときに食わせるJSONの、値の書き方と、モデルの型とのバリデーションの挙動が異なっていました。

違い

以下のコードは、NumericValueA、NumericValueBのプロパティを持つモデルへと、数値データを持っているJSON文字列をデシリアライズするものです。
この時、モデルは、NumericValueA(string型)、NumericValueB(long型)ですが、
食わせるJSONは、NumericValueA(数値)、NumericValueB(文字列)にしてみます。

public sealed class SampleModel
{
    public required string NumericValueA {  get; init; }
    public required long NumericValueB { get; init; }
}

static async Task MainAsync(string[] args)
{
    const string sampleJson = @"{""NumericValueA"":1234, ""NumericValueB"":""5678""}";

    Console.WriteLine("- System.Text.Json - ");
    try
    {
        var data = System.Text.Json.JsonSerializer.Deserialize<SampleModel>(sampleJson);
        Console.Write($"NumericValueA : {data.NumericValueA}  ,  NumericValueB : {data.NumericValueB}");
    }
    catch (Exception ex)
    {
        Console.Write(ex.Message);
    }


    Console.WriteLine("");
    Console.WriteLine("- Newtonsoft.Json - ");
    try
    {
        var data = Newtonsoft.Json.JsonConvert.DeserializeObject<SampleModel>(sampleJson);
        Console.Write($"NumericValueA : {data.NumericValueA}  ,  NumericValueB : {data.NumericValueB}");
    }
    catch (Exception ex)
    {
        Console.Write(ex.Message);
    }
}

このコードを実行したらこうなりました

- System.Text.Json -
The JSON value could not be converted to System.String. Path: $.NumericValueA | LineNumber: 0 | BytePositionInLine: 21.
- Newtonsoft.Json -
NumericValueA : 1234  ,  NumericValueB : 5678

System.Text.Jsonは、string 型なのにJSONが数値になっているので、変換できんと言って例外を吐いています。
Newtonsoft.Jsonは、なんか良しなに埋め込んで、いい感じに変換してくれています()

つまり、System.Text.Json のほうが厳密にデシリアライズしてくれているようです。

System.Text.Json のオプション

System.Text.Json.JsonSerializer.Deserializeの第二引数には、```System.Text.Json.JsonSerializerOptions`` を渡せます。

結構細かく設定ができるので、どうしても互換性を保ってNewtonsoft.JsonからSystem.Text.Jsonに書き換えるときは、JsonSerializerOptionsをなるべく寄せて設定するといいかもしれません。

JsonSerializerOptionsのドキュメントに、各プロパティの説明が書いてあります。
https://learn.microsoft.com/ja-jp/dotnet/api/system.text.json.jsonserializeroptions?view=net-7.0

・・・
でも。。。NumberHandling位しか、有用そうなオプションがないような。。。

一応、以下のようにすると、JSONの値が文字列だった時に、数値型のC#プロパティにコンバートして格納してくれます。
探したけど、JSONの値が数値の時に、文字列型のC#プロパティにコンバートして格納してくれるようなオプションはなさそうでした。

const string sampleJson = @"{""NumericValue"":""1234""}";

var oprtion = new JsonSerializerOptions
{
    NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString
};
var data = System.Text.Json.JsonSerializer.Deserialize<SampleModel>(sampleJson, oprtion);

Console.Write($"NumericValue : {data.NumericValue}");

Discussion