从 Newtonsoft.Json 迁移到 System.Text.Json
本文演示如何从 Newtonsoft.Json 迁移到 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.6.2 及更高版本
- .NET Core 2.0、2.1 和 2.2
提示
可以使用 GitHub Copilot 的 AI 帮助从 Newtonsoft.Json
迁移。
System.Text.Json
主要关注性能、安全性和标准符合性。 它在默认行为方面有一些重要差异,不打算具有与 Newtonsoft.Json
相同的功能。 对于某些方案,System.Text.Json
当前没有内置功能,但有建议解决方法。 对于其他方案,解决方法是不切实际的。
System.Text.Json
团队正在投入精力,添加最常需要的功能。 如果你的应用程序依赖于缺少的功能,请考虑在 dotnet/runtime GitHub 存储库上提交问题,来了解是否可添加对你的方案的支持。
本文的大部分内容介绍如何使用 JsonSerializer API,不过也包含有关如何使用 JsonDocument(表示文档对象模型或 DOM)、Utf8JsonReader 和 Utf8JsonWriter 类型的指导。
在 Visual Basic 中,不能使用 Utf8JsonReader,这也意味着无法编写自定义转换器。 这里介绍的大多数解决方法都要求编写自定义转换器。 可使用 C# 编写自定义转换器,并将其注册到 Visual Basic 项目中。 有关详细信息,请参阅 Visual Basic 支持。
差异表
下表列出 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,可配置 | ✔️ 默认最大深度为 64,可配置 |
PreserveReferencesHandling 全局设置 |
✔️ ReferenceHandling 全局设置 |
序列化或反序列化带引号的数字 | ✔️ NumberHandling 全局设置,[JsonNumberHandling] 特性 |
反序列化为不可变类和结构 | ✔️ JsonConstructor,C# 9 记录 |
支持字段 | ✔️ IncludeFields 全局设置,[JsonInclude] 特性 |
DefaultValueHandling 全局设置 |
✔️ DefaultIgnoreCondition 全局设置 |
NullValueHandling 上的 [JsonProperty] 设置 |
✔️ JsonIgnore 特性 |
DefaultValueHandling 上的 [JsonProperty] 设置 |
✔️ JsonIgnore 特性 |
反序列化具有非字符串键的 Dictionary |
✔️ 受支持 |
支持非公共属性资源库和 Getter | ✔️ JsonInclude 特性 |
[JsonConstructor] 特性 |
✔️ [JsonConstructor] 特性 |
ReferenceLoopHandling 全局设置 |
✔️ ReferenceHandling 全局设置 |
回调 | ✔️ 回调 |
NaN、Infinity、-Infinity | ✔️ 受支持 |
Required 特性上的 [JsonProperty] 设置 |
✔️ [JsonRequired] 特性和 C# 必需的修饰符 |
DefaultContractResolver 用于忽略属性 |
✔️ DefaultJsonTypeInfoResolver 类 |
多态序列化 | ✔️ [JsonDerivedType] 特性 |
多态反序列化 | ✔️ [JsonDerivedType] 特性上的类型鉴别器 |
反序列化字符串枚举值 | ✔️ 反序列化字符串枚举值 |
MissingMemberHandling 全局设置 |
✔️ 处理缺少的成员 |
在没有资源库的情况下填充属性 | ✔️ 在没有资源库的情况下填充属性 |
ObjectCreationHandling 全局设置 |
✔️ 重用而不是替换属性 |
支持范围广泛的类型 | ⚠️ 某些类型需要自定义转换器 |
将推断类型反序列化为 object 属性 |
⚠️ 不受支持,解决方法,示例 |
将 JSON null 文本反序列化为不可为 null 的值类型 |
⚠️ 不受支持,解决方法,示例 |
DateTimeZoneHandling 、DateFormatString 设置 |
⚠️ 不受支持,解决方法,示例 |
JsonConvert.PopulateObject 方法 |
⚠️ 不受支持,解决方法 |
支持 System.Runtime.Serialization 特性 |
⚠️ 不受支持,解决方法,示例 |
JsonObjectAttribute |
⚠️ 不受支持,解决方法 |
允许不带引号的属性名称 | ❌设计上不受支持 |
字符串值前后允许单引号 | ❌设计上不受支持 |
对字符串属性允许非字符串 JSON 值 | ❌设计上不受支持 |
TypeNameHandling.All 全局设置 |
❌设计上不受支持 |
支持 JsonPath 查询 |
❌不支持 |
可配置的限制 | ❌不支持 |
这不是 Newtonsoft.Json
功能的详尽列表。 此列表包含在 GitHub 问题或 StackOverflow 文章中请求的许多方案。 如果对此处所列且当前没有示例代码的一个方案实现了解决方法,并且如果要共享解决方案,请在本页底部的“反馈”部分选择“此页面”。 这会在本文档的 GitHub 存储库中创建一个问题,并将它也列在此页面上的“反馈”部分中。
默认行为的差异
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 字符,因此如果在 StringEscapeHandling.EscapeNonAscii
中使用 Newtonsoft.Json
,则无需执行任何操作。 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
的最新版本的最大深度限制为 64。 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
功能,则无法在不进行重大更改的情况下进行迁移。
允许或写入带引号的数字
Newtonsoft.Json
可以序列化或反序列化由 JSON 字符串表示的数字(括在引号中)。 例如,它可以接受 {"DegreesCelsius":"23"}
而不是 {"DegreesCelsius":23}
。 若要在 System.Text.Json 中启用该行为,请将 JsonSerializerOptions.NumberHandling 设置为 WriteAsString 或 AllowReadingFromString,或使用 [JsonNumberHandling] 特性。
如果使用 ASP.NET Core 间接使用 System.Text.Json
,则无需执行任何操作即可获得类似于 Newtonsoft.Json
的行为。 ASP.NET Core 在使用 时指定 System.Text.Json
,Web 默认值允许带引号的数字。
有关详细信息,请参阅允许或写入带引号的数字。
指定要在反序列化时使用的构造函数
使用 Newtonsoft.Json
[JsonConstructor]
特性可以指定在反序列化为 POCO 时要调用的构造函数。
System.Text.Json
还具有 [JsonConstructor] 特性。 有关详细信息,请参阅不可变类型和记录。
有条件地忽略属性
Newtonsoft.Json
有多种方法可在序列化或反序列化时有条件地忽略属性:
DefaultContractResolver
使你可以基于任意条件选择要包含或忽略的属性。NullValueHandling
上的DefaultValueHandling
和JsonSerializerSettings
设置使你指定应忽略所有 null 值或默认值属性。NullValueHandling
特性上的DefaultValueHandling
和[JsonProperty]
设置使你可以指定在设置为 null 或默认值时应忽略的单个属性。
System.Text.Json 提供以下方法,用于在序列化期间忽略属性或字段:
- 属性上的 [JsonIgnore] 特性会导致在序列化过程中从 JSON 中省略属性。
- IgnoreReadOnlyProperties 全局选项使你可以忽略所有只读属性。
- 如果你包含字段,则 JsonSerializerOptions.IgnoreReadOnlyFields 全局选项使你可以忽略所有只读字段。
- 使用
DefaultIgnoreCondition
全局选项,你可以忽略具有默认值的所有值类型属性,或忽略具有 null 值的所有引用类型属性。
此外,在 .NET 7 及更高版本中,可以自定义 JSON 协定以忽略基于任意条件的属性。 有关详细信息,请参阅自定义协定。
公共和非公共字段
Newtonsoft.Json
可以序列化和反序列化字段以及属性。
在 System.Text.Json 中,在序列化或反序列化时,使用 JsonSerializerOptions.IncludeFields 全局设置或 [JsonInclude] 特性来包含公共字段。 有关示例,请参阅包含字段。
保留对象引用并处理循环
默认情况下,Newtonsoft.Json
按值进行序列化。 例如,如果对象包含两个属性,而这些属性包含对同一个 Person
对象的引用,该 Person
对象属性的值会在 JSON 重复。
Newtonsoft.Json
在 PreserveReferencesHandling
上有一个 JsonSerializerSettings
设置,可让你按引用进行序列化:
- 标识符元数据会添加到为第一个
Person
对象创建的 JSON。 - 为第二个
Person
对象创建的 JSON 包含对该标识符(而不是属性值)的引用。
Newtonsoft.Json
还具有一个 ReferenceLoopHandling
设置,使你可以忽略循环引用,而不是引发异常。
若要在 System.Text.Json 中保留引用并处理循环引用,请将 JsonSerializerOptions.ReferenceHandler 设置为 Preserve。 ReferenceHandler.Preserve
设置等效于 PreserveReferencesHandling
中的 = PreserveReferencesHandling.All
Newtonsoft.Json
。
ReferenceHandler.IgnoreCycles
选项的行为类似于 Newtonsoft.JsonReferenceLoopHandling.Ignore
。 一个区别是,System.Text.Json 实现将引用循环替换为 null
JSON 标记,而不是忽略对象引用。 有关更多信息,请参阅忽略循环引用。
与 Newtonsoft.JsonReferenceResolver 一样,System.Text.Json.Serialization.ReferenceResolver 类定义在序列化和反序列化过程中保留引用的行为。 创建派生类以指定自定义行为。 有关示例,请参阅 GuidReferenceResolver。
一些相关的 Newtonsoft.Json
功能不受支持:
有关详细信息,请参阅保留引用并处理循环引用。
包含非字符串键的字典
Newtonsoft.Json
和 System.Text.Json
都支持 Dictionary<TKey, TValue>
类型的集合。 有关支持的密钥类型的信息,请参阅 支持的密钥类型。
注意
如果反序列化为 Dictionary<TKey, TValue>
(其中 TKey
采用 string
以外的任何类型),则可能会导致使用应用程序中出现安全漏洞。 有关详细信息,请参阅 dotnet/runtime#4761。
没有内置支持的类型
System.Text.Json 不为以下类型提供内置支持:
- DataTable 和相关类型(有关详细信息,请参阅 支持的类型)
- ExpandoObject
- TimeZoneInfo
- BigInteger
- DBNull
- Type
- ValueTuple 及其关联泛型类型
对于没有内置支持的类型,可以实现自定义转换器。
多态序列化
Newtonsoft.Json
会自动执行多态序列化。 从 .NET 7 开始,System.Text.Json 支持通过 JsonDerivedTypeAttribute 特性执行多态序列化。 有关详细信息,请参阅序列化派生类的属性。
多态反序列化
Newtonsoft.Json
具有 TypeNameHandling
设置,它在序列化期间将类型名称元数据添加到 JSON。 它在反序列化期间使用元数据执行多态反序列化。 从 .NET 7 开始,System.Text.Json 依赖于类型鉴别器信息来执行多态反序列化。 此元数据在 JSON 中发出,然后在反序列化期间用于确定是反序列化为基类型还是派生类型。 有关详细信息,请参阅序列化派生类的属性。
若要在旧 .NET 版本中支持多态反序列化,请创建一个转换器,如如何编写自定义转换器中的示例。
反序列化字符串枚举值
默认情况下,System.Text.Json 不支持反序列化字符串枚举值,而 Newtonsoft.Json
则支持。 例如,以下代码会引发 JsonException:
string json = "{ \"Text\": \"Hello\", \"Enum\": \"Two\" }";
var _ = JsonSerializer.Deserialize<MyObj>(json); // Throws exception.
class MyObj
{
public string Text { get; set; } = "";
public MyEnum Enum { get; set; }
}
enum MyEnum
{
One,
Two,
Three
}
但是,你可以使用 JsonStringEnumConverter 转换器启用字符串枚举值的反序列化。 有关详细信息,请参阅枚举作为字符串。
对象属性的反序列化
当 Newtonsoft.Json
反序列化为 Object 时,它会:
- 推断 JSON 有效负载中的基元值的类型(不是
null
),并以装箱对象的形式返回存储的string
、long
、double
、boolean
或DateTime
。 “基元值”是单个 JSON 值,如 JSON 数字、字符串、true
、false
或null
。 - 为 JSON 有效负载中的复杂值返回
JObject
或JArray
。 “复杂值”是括在大括号 ({}
) 中的 JSON 键值对的集合或括在方括号 ([]
) 中的值的列表。 括在大括号或方括号中的属性和值可以具有附加属性或值。 - 当有效负载具有
null
JSON 文本时,返回空引用。
System.Text.Json 在每次反序列化为 JsonElement
时,为基元和复数值存储装箱 Object,例如:
object
属性。object
字典值。object
数组值。- 根
object
。
但是,System.Text.Json
处理 null
的方式与 Newtonsoft.Json
相同,会在有效负载中包含 null
JSON 文本时返回空引用。
若要为 object
属性实现类型推理,请创建转换器(类似于如何编写自定义转换器中的示例)。
将 null 反序列化为不可为 null 的类型
Newtonsoft.Json
在以下方案中不会引发异常:
NullValueHandling
设置为Ignore
,并且- 在反序列化过程中,JSON 对于不可为 null 的值类型包含 null 值。
在相同方案中,System.Text.Json 会引发异常。 (System.Text.Json
中对应的 null 处理设置为 JsonSerializerOptions.IgnoreNullValues = true
。)
如果你拥有目标类型,在最佳解决方法是使相关属性可为 null(例如,将 int
更改为 int?
)。
另一种解决方法是为类型创建转换器,如以下为 DateTimeOffset
类型处理 null 值的示例:
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SystemTextJsonSamples
{
public class DateTimeOffsetNullHandlingConverter : JsonConverter<DateTimeOffset>
{
public override DateTimeOffset Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) =>
reader.TokenType == JsonTokenType.Null
? default
: reader.GetDateTimeOffset();
public override void Write(
Utf8JsonWriter writer,
DateTimeOffset dateTimeValue,
JsonSerializerOptions options) =>
writer.WriteStringValue(dateTimeValue);
}
}
通过对属性使用特性或是通过向 集合Converters来注册此自定义转换器。
注意:前面的转换器处理 null 值的方式与 Newtonsoft.Json
为指定默认值的 POCO 进行处理的方式不同。 例如,假设以下代码表示目标对象:
public class WeatherForecastWithDefault
{
public WeatherForecastWithDefault()
{
Date = DateTimeOffset.Parse("2001-01-01");
Summary = "No summary";
}
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}
并且假设使用前面的转换器反序列化以下 JSON:
{
"Date": null,
"TemperatureCelsius": 25,
"Summary": null
}
反序列化之后,Date
属性具有 1/1/0001 (default(DateTimeOffset)
),即,在构造函数中设置的值会被覆盖。 给定相同 POCO 和 JSON,Newtonsoft.Json
反序列化会将 1/1/2001 保留在 Date
属性中。
反序列化为不可变类和结构
Newtonsoft.Json
可以反序列化为不可变类和结构,因为它可以使用具有参数的构造函数。
在 System.Text.Json 中,使用 [JsonConstructor] 特性来指定参数化构造函数的用法。 C# 9 记录也是不可变的,并且支持作为反序列化目标。 有关详细信息,请参阅不可变类型和记录。
必需的属性
在 Newtonsoft.Json
中,通过对 Required
特性设置 [JsonProperty]
来指定属性是必需的。 如果在 JSON 中没有为标记为必需的属性收到值,Newtonsoft.Json
会引发异常。
从 .NET 7 开始,可以在必需属性上使用 C# required
修饰符或 JsonRequiredAttribute 特性。 如果 JSON 有效负载不包含标记属性的值,System.Text.Json 将引发异常。 有关详细信息,请参阅必需属性。
指定日期格式
Newtonsoft.Json
提供多种方法来控制如何序列化和反序列化 DateTime
和 DateTimeOffset
类型的属性:
DateTimeZoneHandling
设置可用于将所有DateTime
值序列化为 UTC 日期。DateFormatString
设置和DateTime
转换器可用于自定义日期字符串的格式。
System.Text.Json 支持 ISO 8601-1:2019,包括 RFC 3339 配置文件。 此格式被广泛采用,无歧义,并且精确地进行往返。 若要使用任何其他格式,请创建自定义转换器。 例如,以下转换器会序列化和反序列化使用 Unix epoch 格式的 JSON,而无论是否带有时区偏移(/Date(1590863400000-0700)/
或 /Date(1590863400000)/
之类的值):
sealed class UnixEpochDateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
static readonly DateTimeOffset s_epoch = new(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
static readonly Regex s_regex = new("^/Date\\(([+-]*\\d+)([+-])(\\d{2})(\\d{2})\\)/$", RegexOptions.CultureInvariant);
public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
string formatted = reader.GetString()!;
Match match = s_regex.Match(formatted);
if (
!match.Success
|| !long.TryParse(match.Groups[1].Value, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture, out long unixTime)
|| !int.TryParse(match.Groups[3].Value, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture, out int hours)
|| !int.TryParse(match.Groups[4].Value, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture, out int minutes))
{
throw new JsonException();
}
int sign = match.Groups[2].Value[0] == '+' ? 1 : -1;
TimeSpan utcOffset = new(hours * sign, minutes * sign, 0);
return s_epoch.AddMilliseconds(unixTime).ToOffset(utcOffset);
}
public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
{
long unixTime = Convert.ToInt64((value - s_epoch).TotalMilliseconds);
TimeSpan utcOffset = value.Offset;
string formatted = string.Create(CultureInfo.InvariantCulture, $"/Date({unixTime}{(utcOffset >= TimeSpan.Zero ? "+" : "-")}{utcOffset:hhmm})/");
writer.WriteStringValue(formatted);
}
}
sealed class UnixEpochDateTimeConverter : JsonConverter<DateTime>
{
static readonly DateTime s_epoch = new(1970, 1, 1, 0, 0, 0);
static readonly Regex s_regex = new("^/Date\\(([+-]*\\d+)\\)/$", RegexOptions.CultureInvariant);
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
string formatted = reader.GetString()!;
Match match = s_regex.Match(formatted);
if (
!match.Success
|| !long.TryParse(match.Groups[1].Value, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture, out long unixTime))
{
throw new JsonException();
}
return s_epoch.AddMilliseconds(unixTime);
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
long unixTime = Convert.ToInt64((value - s_epoch).TotalMilliseconds);
string formatted = string.Create(CultureInfo.InvariantCulture, $"/Date({unixTime})/");
writer.WriteStringValue(formatted);
}
}
有关详细信息,请参阅 System.Text.Json 中的 DateTime 和 DateTimeOffset 支持。
回调
Newtonsoft.Json
使你可以在序列化或反序列化过程中的多个点执行自定义代码:
- OnDeserializing(开始反序列化对象时)
- OnDeserialized(对象反序列化完成时)
- OnSerializing(开始序列化对象时)
- OnSerialized(对象序列化完成时)
System.Text.Json 在序列化和反序列化期间公开相同的通知。 若要使用它们,请从 System.Text.Json.Serialization 命名空间实现以下一个或多个接口:
以下示例会检查 null 属性,并在序列化和反序列化开始和结束时编写消息:
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Callbacks
{
public class WeatherForecast :
IJsonOnDeserializing, IJsonOnDeserialized,
IJsonOnSerializing, IJsonOnSerialized
{
public DateTime Date { get; set; }
public int TemperatureCelsius { get; set; }
public string? Summary { get; set; }
void IJsonOnDeserializing.OnDeserializing() => Console.WriteLine("\nBegin deserializing");
void IJsonOnDeserialized.OnDeserialized()
{
Validate();
Console.WriteLine("Finished deserializing");
}
void IJsonOnSerializing.OnSerializing()
{
Console.WriteLine("Begin serializing");
Validate();
}
void IJsonOnSerialized.OnSerialized() => Console.WriteLine("Finished serializing");
private void Validate()
{
if (Summary is null)
{
Console.WriteLine("The 'Summary' property is 'null'.");
}
}
}
public class Program
{
public static void Main()
{
var weatherForecast = new WeatherForecast
{
Date = DateTime.Parse("2019-08-01"),
TemperatureCelsius = 25,
};
string jsonString = JsonSerializer.Serialize(weatherForecast);
Console.WriteLine(jsonString);
weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString);
Console.WriteLine($"Date={weatherForecast?.Date}");
Console.WriteLine($"TemperatureCelsius={weatherForecast?.TemperatureCelsius}");
Console.WriteLine($"Summary={weatherForecast?.Summary}");
}
}
}
// output:
//Begin serializing
//The 'Summary' property is 'null'.
//Finished serializing
//{"Date":"2019-08-01T00:00:00","TemperatureCelsius":25,"Summary":null}
//Begin deserializing
//The 'Summary' property is 'null'.
//Finished deserializing
//Date=8/1/2019 12:00:00 AM
//TemperatureCelsius = 25
//Summary=
OnDeserializing
代码无权访问新 POCO 实例。 若要在反序列化开始时操作新 POCO 实例,请将该代码放入 POCO 构造函数中。
非公共属性资源库和 Getter
Newtonsoft.Json
可以通过 JsonProperty
特性使用私有和内部属性 setter 和 getter。
System.Text.Json 支持通过 [JsonInclude] 特性使用私有和内部属性资源库和 Getter。 有关示例代码,请参阅非公共属性访问器。
填充现有对象
JsonConvert.PopulateObject
中的 Newtonsoft.Json
方法将 JSON 文档反序列化为类的现有实例,而不是创建新实例。 System.Text.Json 始终使用默认公共无参数构造函数创建目标类型的新实例。 自定义转换器可以反序列化为现有实例。
重用而不是替换属性
从 .NET 8 开始, System.Text.Json 支持重用初始化的属性,而不是替换它们。 行为存在一些差异,可以在 API 建议中了解这些差异。
有关详细信息,请参阅填充初始化的属性。
在没有资源库的情况下填充属性
从 .NET 8 开始,System.Text.Json 支持填充属性,包括没有资源库的属性。 有关详细信息,请参阅填充初始化的属性。
蛇形命名法命名策略
System.Text.Json 包括蛇形命名法的内置命名策略。 但是,对于某些输入,Newtonsoft.Json
存在一些行为差异。 下表显示了使用 JsonNamingPolicy.SnakeCaseLower 策略转换输入时的一些差异。
输入 | Newtonsoft.Json 结果 | System.Text.Json 结果 |
---|---|---|
"AB1" | "a_b1" | "ab1" |
"SHA512Managed" | "sh_a512_managed" | "sha512_managed" |
"abc123DEF456" | "abc123_de_f456" | "abc123_def456" |
"KEBAB-CASE" | "keba_b-_case" | "kebab-case" |
System.Runtime.Serialization 特性
使用 System.Runtime.Serialization、DataContractAttribute 和 DataMemberAttribute 等 IgnoreDataMemberAttribute 属性可以定义数据协定。 “数据协定”是在服务与客户端之间达成的正式协议,用于以抽象方式描述要交换的数据。 数据协定精确定义要序列化哪些属性以便交换。
System.Text.Json 没有对这些属性的内置支持。 但是,从 .NET 7 开始,可以使用自定义类型解析程序来添加支持。 有关示例,请参阅 ZCS.DataContractResolver。
八进制数字
Newtonsoft.Json
将带前导零的数字视为八进制数字。 System.Text.Json 不允许存在前导零,因为 RFC 8259 规范不允许。
处理所有缺少的成员
如果正在反序列化的 JSON 包含目标类型中缺少的属性,则可以将 Newtonsoft.Json
配置为引发异常。 默认情况下,System.Text.Json 会忽略 JSON 中的额外属性,但在使用 [JsonExtensionData] 特性时除外。
在 .NET 8 及更高版本中,可以使用以下方式之一来设置是否跳过或禁止未映射 JSON 属性的首选项:
- 将 JsonUnmappedMemberHandlingAttribute 特性应用于要反序列化成的类型。
- 若要全局设置首选项,请设置 JsonSerializerOptions.UnmappedMemberHandling 属性。 或者,对于源生成,请设置 JsonSourceGenerationOptionsAttribute.UnmappedMemberHandling 属性,并将该特性应用于 JsonSerializerContext 类。
- 自定义 JsonTypeInfo.UnmappedMemberHandling 属性。
JsonObjectAttribute
Newtonsoft.Json
具有属性 JsonObjectAttribute
,可在类型级别应用该属性,用于控制序列化哪些成员、如何处理 null
值以及是否需要所有成员。 System.Text.Json 没有可应用于类型的等效属性。 对于某些行为(例如 null
值处理),可以在全局 JsonSerializerOptions 或每个属性上单独配置相同的行为。
请考虑以下示例,该示例使用 Newtonsoft.Json.JsonObjectAttribute
指定应忽略所有 null
属性:
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
public class Person { ... }
在 System.Text.Json 中,可以设置所有类型和属性的行为:
JsonSerializerOptions options = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
string json = JsonSerializer.Serialize<Person>(person, options);
或者,可以单独设置每个属性的行为:
public class Person
{
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Name { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public int? Age { get; set; }
}
接下来,请考虑以下示例,该示例使用 Newtonsoft.Json.JsonObjectAttribute
指定所有成员属性必须存在于 JSON 中:
[JsonObject(ItemRequired = Required.Always)]
public class Person { ... }
你可以通过将 C# System.Text.Json 修饰符或 required
JsonRequiredAttribute,在 中实现相同的行为。 有关详细信息,请参阅必需属性。
public class Person
{
[JsonRequired]
public string? Name { get; set; }
public required int? Age { get; set; }
}
TraceWriter
Newtonsoft.Json
使你可以使用 TraceWriter
进行调试,以查看序列化或反序列化所生成的日志。 System.Text.Json 不执行日志记录。
与 JToken(如 JObject、JArray)相比的 JsonDocument 和 JsonElement
System.Text.Json.JsonDocument 提供从现有 JSON 有效负载分析和生成只读文档对象模型 (DOM) 的功能。 DOM 提供对 JSON 有效负载中的数据的随机访问。 可以通过 JsonElement 类型访问构成有效负载的 JSON 元素。 JsonElement
类型提供用于将 JSON 文本转换为常见 .NET 类型的 API。 JsonDocument
公开了 RootElement 属性。
从 .NET 6 开始,可通过使用 命名空间中的 JsonNode 类型和其他类型,从现有 JSON 有效负载分析和生成System.Text.Json.Nodes DOM。 有关详细信息,请参阅使用 JsonNode
。
JsonDocument 为 IDisposable
JsonDocument
将内存中的数据视图生成到共用缓冲区中。 因此,与 JObject
中的 JArray
或 Newtonsoft.Json
不同,JsonDocument
类型实现 IDisposable
并且需要在 using 块中使用。 有关详细信息,请参阅 JsonDocument 是 IDisposable。
JsonDocument 为只读
System.Text.Json DOM 无法添加、删除或修改 JSON 元素。 它这样设计是为了实现性能,并减少用于分析常见 JSON 有效负载大小(即 < 1 MB)的分配。
JsonElement 是联合结构
JsonDocument
将 RootElement
公开为类型 JsonElement 的属性,该类型是包含任何 JSON 元素的联合结构类型。 Newtonsoft.Json
使用专用分层类型,如 JObject
、JArray
、JToken
等。 JsonElement
是可以搜索和枚举的内容,你可以使用 JsonElement
将 JSON 元素具体化为 .NET 类型。
从 .NET 6 开始,可使用 JsonNode 类型,以及 System.Text.Json.Nodes 命名空间中与 JObject
、JArray
和 JToken
对应的类型。 有关详细信息,请参阅使用 JsonNode
。
如何搜索子元素的 JsonDocument 和 JsonElement
使用 JObject
中的 JArray
或 Newtonsoft.Json
搜索 JSON 令牌的速度往往相对较快,因为它们是在某个字典中查找。 相比之下,若要对 JsonElement
进行搜索,需要对属性进行线性搜索,因此速度相对较慢(例如在使用 TryGetProperty
时)。 System.Text.Json 旨在最大程度减少初始分析时间,而不是查找时间。 有关详细信息,请参阅如何搜索子元素的 JsonDocument 和 JsonElement。
Utf8JsonReader 与JsonTextReader
System.Text.Json.Utf8JsonReader 是面向 UTF-8 编码 JSON 文本的一个高性能、低分配的只进读取器,从 ReadOnlySpan<byte> 或 ReadOnlySequence<byte> 读取信息。 Utf8JsonReader
是一种低级类型,可用于生成自定义分析器和反序列化程序。
Utf8JsonReader 是 ref struct
JsonTextReader
中的 Newtonsoft.Json
是一个类。 Utf8JsonReader
类型的不同之处在于它是一个 ref struct。 有关详细信息,请参阅 Utf8JsonReader 的 ref 结构限制。
将 null 值读取到可为 null 的值类型中
Newtonsoft.Json
提供返回 Nullable<T> 的 API,如 ReadAsBoolean
(它通过返回 Null
来处理 TokenType
bool?
)。 内置 System.Text.Json
API 仅返回不可为 null 的值类型。 有关详细信息,请参阅将 null 值读取到可为 null 的值类型。
用于读取 JSON 的多目标
如果需要继续为某些目标框架使用 Newtonsoft.Json
,则可以使用多目标,并具有两种实现。 但是,这并非易事,需要进行一些 #ifdefs
和源文件复制。 要共享尽可能多代码,一种方法是围绕 ref struct
和 Utf8JsonReader 创建 Newtonsoft.Json.JsonTextReader
包装器。 此包装器会统一公共外围应用,同时隔离行为差异。 这使你可以隔离主要对类型的构造进行的更改,以及按引用传递新类型。 下面是 Microsoft.Extensions.DependencyModel 库遵循的模式:
Utf8JsonWriter 与JsonTextWriter
System.Text.Json.Utf8JsonWriter 是一种高性能方式,从常见 .NET 类型(例如,String
、Int32
和 DateTime
)编写 UTF-8 编码的 JSON 文本。 该编写器是一种低级类型,可用于生成自定义序列化程序。
编写原始值
Newtonsoft.Json
具有 WriteRawValue
方法,它编写需要值的原始 JSON。 System.Text.Json 具有直接等效项:Utf8JsonWriter.WriteRawValue。 有关详细信息,请参阅写入原始 JSON。
自定义 JSON 格式
JsonTextWriter
包含以下设置(Utf8JsonWriter 对于它们没有等效项):
- QuoteChar - 指定要用于围绕字符串值的字符。
Utf8JsonWriter
始终使用双引号。 - QuoteName - 指定是否要使用引号围绕属性名称。
Utf8JsonWriter
始终使用引号围绕它们。
从 .NET 9 开始,可使用 Utf8JsonWriter 结构公开的选项自定义 JsonWriterOptions 的缩进字符和大小:
JsonTextWriter
包含以下设置(Utf8JsonWriter
对于它们没有等效项):
- 缩进 - 指定要缩进的字符数。
Utf8JsonWriter
始终缩进 2 个字符。 - IndentChar - 指定要用于缩进的字符。
Utf8JsonWriter
始终使用空格。 - QuoteChar - 指定要用于围绕字符串值的字符。
Utf8JsonWriter
始终使用双引号。 - QuoteName - 指定是否要使用引号围绕属性名称。
Utf8JsonWriter
始终使用引号围绕它们。
没有解决方法可让你自定义 Utf8JsonWriter
以这些方式生成的 JSON。
编写 Timespan、Uri 或 char 值
JsonTextWriter
提供 WriteValue
方法以用于 TimeSpan、Uri 和 char 值。 Utf8JsonWriter
没有等效方法。 而是将这些值格式化为字符串(例如,通过调用 ToString()
)并调用 WriteStringValue。
用于写入 JSON 的多目标
如果需要继续为某些目标框架使用 Newtonsoft.Json
,则可以使用多目标,并具有两种实现。 但是,这并非易事,需要进行一些 #ifdefs
和源文件复制。 共享尽可能多代码的一种方法是围绕 Utf8JsonWriter 和 Newtonsoft.Json.JsonTextWriter
创建包装器。 此包装器会统一公共外围应用,同时隔离行为差异。 这使你可以隔离主要对类型的构造进行的更改。 Microsoft.Extensions.DependencyModel 库遵循:
不支持 TypeNameHandling.All
故意从 TypeNameHandling.All
中排除了 System.Text.Json
的等效功能。 允许 JSON 有效负载指定自己的类型信息是导致 Web 应用程序中出现漏洞的常见原因。 具体来说,通过使用 Newtonsoft.Json
配置 TypeNameHandling.All
,远程客户端可在 JSON 有效负载自身内部嵌入整个可执行应用程序,因此在反序列化过程中,Web 应用程序会提取并运行嵌入代码。 有关详细信息,请参阅 Friday the 13th JSON attacks PowerPoint(13 日周五的 JSON 攻击 PowerPoint)和 Friday the 13th JSON attacks details(13 日周五的 JSON 攻击细节)。
不支持 JSON 路径查询
JsonDocument
DOM 不支持使用“JSON 路径”进行查询。
在 JsonNode DOM 中,每个 JsonNode
实例都有一个 GetPath
方法,它会返回该节点的路径。 但是,没有内置 API 来处理基于 JSON 路径查询字符串的查询。
有关详细信息,请参阅 GitHub 问题 dotnet/runtime #31068。
某些限制不可配置
System.Text.Json 设置了对某些值来说无法更改的限制,例如最大标记大小以字符为单位时为 166 MB,以 base 64 为单位时为 125 MB。 有关详细信息,请参阅JsonConstants
源代码和 GitHub 问题 dotnet/runtime #39953。
NaN、Infinity、-Infinity
Newtonsoft 会分析 NaN
、Infinity
和 -Infinity
JSON 字符串标记。 对于 System.Text.Json,请使用 JsonNumberHandling.AllowNamedFloatingPointLiterals。 若要了解如何使用此设置,请参阅允许或写入引号中的数字。
使用 GitHub Copilot 迁移
可以从 GitHub Copilot 获取编码帮助,以便将代码从 Newtonsoft.Json
迁移到 IDE 中的 System.Text.Json
。 可以根据要求自定义提示。
Copilot Chat 示例提示
convert the following code to use System.Text.Json
Product product = new Product();
product.Name = "Apple";
product.ExpiryDate = new DateTime(2024, 08, 08);
product.Price = 3.99M;
product.Sizes = new string[] { "Small", "Medium", "Large" };
string output = JsonConvert.SerializeObject(product);
Console.WriteLine(output);
GitHub Copilot 由 AI 提供支持,因此可能会带来意外和错误。 有关详细信息,请参阅 Copilot 常见问题解答。
详细了解 Visual Studio 中的