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 里没有定义的 City
和 Title
此时在使用上面的 Model 就会出现信息丢失,Title
和 City
的信息就会丢掉了,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));
输出结果如下:
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 文件