前言
可能有的小伙伴已经知道了,在.NET Core 3.0
中微软加入了对JSON
的内置支持。 一直以来.NET
开发者们已经习惯使用Json.NET
这个强大的库来处理JSON
。 那么.NET
为什么要增加JSON
的内置支持呢? 最近,.NET
的官方博客再次发表文章说明了这么做的原因、并介绍了相关API
的用法。原文地址: https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/
。那么,现在就结合自身的理解来分析一下Text.Json
和Json.NET
的区别。
System.Text.Json介绍
System.Text.Json
命名空间提供用于序列化和反序列化 JavaScript 对象表示法 (JSON) 的功能。 System.Text.Json
库包含在 .NET Core 3.1 和更高版本的运行时中。 对于其他目标框架,请安装 System.Text.Json NuGet 包。 包支持以下框架:
- .NET Standard 2.0 及更高版本
- .NET Framework 4.7.2 及更高版本
- .NET Core 2.0、2.1 和 2.2
System.Text.Json
主要关注性能、安全性和标准符合性。 它在默认行为方面有一些重要差异,不打算具有与Newtonsoft.Json
相同的功能。 对于某些方案,System.Text.Json
没有内置功能,但有建议解决方法。
Newtonsoft.Json 与 System.Text.Json 之间差异
- 受支持功能支持。 从
System.Text.Json
获取类似行为可能需要使用特性或全局选项。 - 不受支持,可能有解决方法。 解决方法是自定义转换器,它们可能无法提供与
Newtonsoft.Json
功能完全相同的功能。 对于其中一些功能,提供示例代码作为示例。 如果你依赖于这些Newtonsoft.Json
功能,迁移需要修改 .NET 对象模型或进行其他代码更改。 - 不受支持,解决方法不可行或无法提供。 如果你依赖于这些
Newtonsoft.Json
功能,则无法在不进行重大更改的情况下进行迁移。 Newtonsoft.Json 功能 | System.Text.Json 等效 | | | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 默认情况下不区分大小写的反序列化 | ✔️ PropertyNameCaseInsensitive 全局设置 | | | Camel 大小写属性名称 | ✔️ PropertyNamingPolicy 全局设置 | | | 最小字符转义 | ✔️ 严格字符转义,可配置 | | |NullValueHandling.Ignore
全局设置 | ✔️ DefaultIgnoreCondition 全局选项 | 有条件地忽略属性 | | 允许注释 | ✔️ ReadCommentHandling 全局设置 | | | 允许尾随逗号 | ✔️ AllowTrailingCommas 全局设置 | | | 自定义转换器注册 | ✔️ 优先级顺序不同 | | | 默认情况下无最大深度 | ✔️ 默认最大深度为 64,可配置 | | |PreserveReferencesHandling
全局设置 | ✔️ ReferenceHandling 全局设置 | | | 序列化或反序列化带引号的数字 | ✔️ NumberHandling 全局设置,[JsonNumberHandling] 特性 | | | 反序列化为不可变类和结构 | ✔️ JsonConstructor,C# 9 记录 | | | 支持字段 | ✔️ IncludeFields 全局设置,[JsonInclude] 特性 | | |DefaultValueHandling
全局设置 | ✔️ DefaultIgnoreCondition 全局设置 | | |[JsonProperty]
上的NullValueHandling
设置 | ✔️ JsonIgnore 特性 | | |[JsonProperty]
上的DefaultValueHandling
设置 | ✔️ JsonIgnore 特性 | | | 反序列化具有非字符串键的Dictionary
| ✔️ 受支持 | | | 支持非公共属性资源库和 Getter | ✔️ JsonInclude 特性 | | |[JsonConstructor]
特性 | ✔️ [JsonConstructor] 特性 | | | 支持范围广泛的类型 | ⚠️ 某些类型需要自定义转换器 | | | 多态序列化 | ⚠️ 不受支持,解决方法,示例 | | | 多态反序列化 | ⚠️ 不受支持,解决方法,示例 | | | 将推断类型反序列化为object
属性 | ⚠️ 不受支持,解决方法,示例 | | | 将 JSONnull
文本反序列化为不可为 null 的值类型 | ⚠️ 不受支持,解决方法,示例 | | |[JsonProperty]
特性上的Required
设置 | ⚠️ 不受支持,解决方法,示例 | | |DefaultContractResolver
用于忽略属性 | ⚠️ 不受支持,解决方法,示例 | | |DateTimeZoneHandling
、DateFormatString
设置 | ⚠️ 不受支持,解决方法,示例 | | | 回调 | ⚠️ 不受支持,解决方法,示例 | | |JsonConvert.PopulateObject
方法 | ⚠️ 不受支持,解决方法 | | |ObjectCreationHandling
全局设置 | ⚠️ 不受支持,解决方法 | | | 在不带 setter 的情况下添加到集合 | ⚠️ 不受支持,解决方法 | | |ReferenceLoopHandling
全局设置 | ❌ 不受支持 | | | 支持System.Runtime.Serialization
特性 | ❌ 不受支持 | | |MissingMemberHandling
全局设置 | ❌ 不受支持 | | | 允许不带引号的属性名称 | ❌ 不受支持 | | | 字符串值前后允许单引号 | ❌ 不受支持 | | | 对字符串属性允许非字符串 JSON 值 | ❌ 不受支持
默认 JsonSerializer 行为相较于 Newtonsoft.Json 的差异
System.Text.Json 在默认情况下十分严格,避免代表调用方进行任何猜测或解释,强调确定性行为。 该库是为了实现性能和安全性而特意这样设计的。 Newtonsoft.Json
默认情况下十分灵活。 设计中的这种根本差异是默认行为中以下许多特定差异的背后原因。
不区分大小写的反序列化
在反序列化过程中,默认情况下 Newtonsoft.Json
进行不区分大小写的属性名称匹配。 System.Text.Json 默认值区分大小写,这可提供更好的性能,因为它执行精确匹配。 有关如何执行不区分大小写的匹配的信息,请参阅不区分大小写的属性匹配。
如果使用 ASP.NET Core 间接使用 System.Text.Json
,则无需执行任何操作即可获得类似于 Newtonsoft.Json
的行为。 ASP.NET Core 在使用 System.Text.Json
时,会为 camel 大小写属性名称和不区分大小写的匹配指定设置。
默认情况下,ASP.NET Core 还允许反序列化带引号的数字。
最小字符转义
在序列化过程中,Newtonsoft.Json
对于让字符通过而不进行转义相对宽松。 也就是说,它不会将它们替换为 \uxxxx
(其中 xxxx
是字符的码位)。 对字符进行转义时,它会通过在字符前发出 `` 来实现此目的(例如,"
会变为 "
)。 System.Text.Json 会在默认情况下转义较多字符,以对跨站点脚本 (XSS) 或信息泄露攻击提供深度防御保护,并使用六字符序列执行此操作。 System.Text.Json
会在默认情况下转义所有非 ASCII 字符,因此如果在 Newtonsoft.Json
中使用 StringEscapeHandling.EscapeNonAscii
,则无需执行任何操作。 System.Text.Json
在默认情况下还会转义 HTML 敏感字符。 有关如何替代默认 System.Text.Json
行为的信息,请参阅自定义字符编码。
注释
在反序列化过程中,Newtonsoft.Json
在默认情况下会忽略 JSON 中的注释。 System.Text.Json 默认值是对注释引发异常,因为 RFC 8259 规范不包含它们。 有关如何允许注释的信息,请参阅允许注释和尾随逗号。
尾随逗号
在反序列化过程中,默认情况下 Newtonsoft.Json
会忽略尾随逗号。 它还会忽略多个尾随逗号(例如 [{"Color":"Red"},{"Color":"Green"},,]
)。 System.Text.Json 默认值是对尾随逗号引发异常,因为 RFC 8259 规范不允许使用它们。 有关如何使 System.Text.Json
接受它们的信息,请参阅允许注释和尾随逗号。 无法允许多个尾随逗号。
转换器注册优先级
自定义转换器的 Newtonsoft.Json
注册优先级如下所示:
- 属性上的特性
- 类型上的特性
- 转换器 集合
此顺序意味着 Converters
集合中的自定义转换器会由通过在类型级别应用特性而注册的转换器替代。 这两个注册都会由属性级别的特性替代。
自定义转换器的 System.Text.Json 注册优先级是不同的:
- 属性上的特性
- Converters 集合
- 类型上的特性
此处的差别在于 Converters
集合中的自定义转换器会替代类型级别的特性。 此优先级顺序的目的是使运行时更改替代设计时选项。 无法更改优先级。
有关自定义转换器注册的详细信息,请参阅注册自定义转换器。
最大深度
Newtonsoft.Json
默认情况下没有最大深度限制。 对于 System.Text.Json,默认限制为 64,可通过设置 JsonSerializerOptions.MaxDepth 进行配置。
如果使用 ASP.NET Core 时间接使用 System.Text.Json
,则默认的最大深度限制为 32。 默认值与模型绑定的默认值相同,并且在 JsonOptions 类中设置。
JSON 字符串(属性名称和字符串值)
在反序列化过程中,Newtonsoft.Json
接受用双引号、单引号括起来或不带引号的属性名称。 它接受用双引号或单引号括起来的字符串值。 例如,Newtonsoft.Json
接受以下 JSON:
{
"name1": "value",
'name2': "value",
name3: 'value'
}
System.Text.Json
仅接受双引号中的属性名称和字符串值,因为 RFC 8259 规范要求使用该格式,这是唯一视为有效 JSON 的格式。
用单引号括起来的值会导致 JsonException,并出现以下消息:
''' is an invalid start of a value.
字符串属性的非字符串值
Newtonsoft.Json
接受非字符串值(如数字或文本 true
和 false
),以便反序列化为类型字符串的属性。 下面是 Newtonsoft.Json
成功反序列化为以下类的 JSON 示例:
{
"String1": 1,
"String2": true,
"String3": false
}
public class ExampleClass
{
public string String1 { get; set; }
public string String2 { get; set; }
public string String3 { get; set; }
}
System.Text.Json
不将非字符串值反序列化为字符串属性。 字符串字段接收的非字符串值会导致 JsonException,并出现以下消息:
The JSON value could not be converted to System.String.
使用 JsonSerializer 的方案
下面一部分方案不受内置功能支持,但有解决方法可用。 解决方法是自定义转换器,它们可能无法提供与 Newtonsoft.Json
功能完全相同的功能。 对于其中一些功能,提供示例代码作为示例。 如果你依赖于这些 Newtonsoft.Json
功能,迁移需要修改 .NET 对象模型或进行其他代码更改。
对于下面的一部分方案,解决方法不可行或无法提供。 如果你依赖于这些 Newtonsoft.Json
功能,则无法在不进行重大更改的情况下进行迁移。具体参考:## 使用 JsonSerializer 的方案
性能
由于此功能受性能的强烈推动,我们希望分享新API的一些高级性能特征。
请记住,这些都是基于预览版本,最终数字很可能会有所不同。我们还在调整会影响性能的默认行为(例如,区分大小写)。请注意,这些都是微基准测试。您的里程肯定会有所不同,因此如果性能对您至关重要,请确保针对最能代表您工作负载的方案进行自己的测量。
原生 System.Text.Json
只需进行微基准测试即可 System.Text.Json
与Json.NET 进行比较 ,得出以下结果:
场景 | 速度 | 内存 |
---|---|---|
反序列化 | 快2倍 | 持平或更低 |
序列化 | 快1.5倍 | 持平或更低 |
文件(只读) | 快3-5倍 | <1 MB无分配 |
读取器 | 快2-3倍 | 无分配(直到实现值(materialize values)) |
写入器 | 快1.3-1.6倍 | 无分配 |
ASP.NET Core MVC 中的 System.Text.Json
我们编写了一个ASP.NET Core应用程序,可以动态生成 数据 ,然后从MVC控制器进行序列化和反序列化 。然后我们改变有效载荷大小并测量结果:
JSON反序列化(输入)
描述 | 吞吐量(RPS) | CPU (%) | 内存 (MB) |
---|---|---|---|
Newtonsoft.Json – 500 B | 136,435 | 95 | 172 |
System.Text.Json – 500 B | 167,861 | 94 | 169 |
Newtonsoft.Json – 2.4 KB | 97,137 | 97 | 174 |
System.Text.Json – 2.4 KB | 32,026 | 96 | 169 |
Newtonsoft.Json – 40 KB | 7,712 | 88 | 212 |
System.Text.Json – 40 KB | 16,625 | 96 | 193 |
JSON序列化(输出)
描述 | 吞吐量(RPS) | CPU(%) | 内存(MB) |
---|---|---|---|
Newtonsoft.Json - 500 B | 120,273 | 94 | 174 |
System.Text.Json - 500 B | 145,631 | 94 | 173 |
Newtonsoft.Json - 8 KB | 35,408 | 98 | 187 |
System.Text.Json - 8 KB | 56,424 | 97 | 184 |
Newtonsoft.Json - 40 KB | 8,416 | 99 | 202 |
System.Text.Json - 40 KB | 14,848 | 98 | 197 |
对于最常见的有效负载大小, System.Text.Json
在输入和输出格式化期间,MVC的吞吐量增加约20%,内存占用量更小。