System.Text.Json 中的 JsonExtensionData

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了System.Text.Json 中的 JsonExtensionData相关的知识,希望对你有一定的参考价值。

System.Text.Json 中的 JsonExtensionData

Intro

最近两天在排查我们 API 的一个问题,查看源码过程中发现 System.Text.Json 里有一个有意思的 JsonExtensionData

在反序列化的时候,如果反序列化的 Model 中没有对应的属性信息,这些信息就会丢失,只会保留 Model 里有的数据,而 JsonExtensionData 则可以将这些没有对应属性的信息也保留下来,在序列化的时候也会保留下来。

Sample

直接来看示例吧:

定义的 Model 如下,这里使用了 C# 9 引入的 record 来简化代码

public record Person(string Name, int Age);

如果我们的 JSON 字符串正好只有这两个属性的话

JsonSerializer.Serialize(new
        {
            Name = "Ming",
            Age = 10,
        });

如果是这样的,那么也不会有什么问题

如果 JSON 字符串会有更多的信息,比如:

JsonSerializer.Serialize(new
        {
            Name = "Ming",
            Age = 10,
            Title = "SDE",
            City = "Shanghai"
        });

可以看到,这个 JSON 会有更多的信息,会包含 Model 里没有定义的 CityTitle

此时在使用上面的 Model 就会出现信息丢失,TitleCity 的信息就会丢掉了,System.Text.Json 提供了一种方式 JsonExtensionData  来保存这些在 Model 里没有定义的属性/字段信息

使用 JsonExtensionData 的属性/字段有类型要求,需要是以下三种类型之一:

  • IDictionary<string, object>

  • IDictionary<string, JsonElement>

  • JsonObject(.NET 6 新增支持)

于是我们就有了下面的测试 Model

public record Person(string Name, int Age);

public record Person1(string Name, int Age) : Person(Name, Age)
{
    [JsonExtensionData]
    public Dictionary<string, object?> Extensions { get; set; } = new();
}

public record Person2(string Name, int Age) : Person(Name, Age)
{
    [JsonExtensionData]
    public Dictionary<string, JsonElement> Extensions { get; set; } = new(StringComparer.OrdinalIgnoreCase);
}

public record Person3(string Name, int Age) : Person(Name, Age)
{
    [JsonExtensionData]
    public JsonObject? Extensions { get; set; }
}

测试代码如下:

var p1 = JsonSerializer.Deserialize<Person1>(jsonString);
ArgumentNullException.ThrowIfNull(p1, nameof(p1));
WriteLine(JsonSerializer.Serialize(p1.Extensions));

var p2 = JsonSerializer.Deserialize<Person2>(jsonString);
ArgumentNullException.ThrowIfNull(p2, nameof(p2));
WriteLine(JsonSerializer.Serialize(p2.Extensions));

var p3 = JsonSerializer.Deserialize<Person3>(jsonString);
ArgumentNullException.ThrowIfNull(p3, nameof(p3));
WriteLine(JsonSerializer.Serialize(p3.Extensions));

输出结果如下:

可以看到使用了 JsonExtensionData 之后,多余的信息也会保存下来,把 Extensions 打印一下都是一样的结果

Extensions 中保存了我们没有匹配到的信息,这样我们就可以获取到那些可能会丢失掉的数据了

我们可以把整个对象直接打印出来

using static System.Console;

var p = JsonSerializer.Deserialize<Person>(jsonString);
ArgumentNullException.ThrowIfNull(p, nameof(p));
WriteLine(JsonSerializer.Serialize(p));

var p1 = JsonSerializer.Deserialize<Person1>(jsonString);
ArgumentNullException.ThrowIfNull(p1, nameof(p1));
WriteLine(JsonSerializer.Serialize(p1));

var p2 = JsonSerializer.Deserialize<Person2>(jsonString);
ArgumentNullException.ThrowIfNull(p2, nameof(p2));
WriteLine(JsonSerializer.Serialize(p2));

var p3 = JsonSerializer.Deserialize<Person3>(jsonString);
ArgumentNullException.ThrowIfNull(p3, nameof(p3));
WriteLine(JsonSerializer.Serialize(p3));
WriteLine(new string('-', 20));

输出结果如下:

output

More

借助 JsonExtensionData 我们可以实现一些比较灵活的扩展,没有用过的童鞋不妨试一下

细心的童鞋可能会发现最后一个输出的结果会有一些不同,这是一个 BUG 可以参考 issue: https://github.com/dotnet/runtime/issues/60806

上述示例可以在 Github 获取 https://github.com/WeihanLi/SamplesInPractice/blob/master/JsonSample/SystemTextJsonSample/JsonExtensionDataSample.cs

References

  • https://github.com/WeihanLi/SamplesInPractice/blob/master/JsonSample/SystemTextJsonSample/JsonExtensionDataSample.cs

  • https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-handle-overflow?WT.mc_id=DT-MVP-5004222

  • https://docs.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonextensiondataattribute?WT.mc_id=DT-MVP-5004222

  • https://github.com/dotnet/runtime/issues/61080

以上是关于System.Text.Json 中的 JsonExtensionData的主要内容,如果未能解决你的问题,请参考以下文章

.NET 6 中的七个 System.Text.Json 特性

如何反序列化作为 System.Text.Json 中的字符串的嵌套 JSON 对象?

.NET 6 新特性 System.Text.Json 中的 Writeable DOM

使用 System.Text.Json 修改 JSON 文件

使用 .NET core 3.0/System.text.Json 解析 JSON 文件

System.Text.Json:如何为枚举值指定自定义名称?