System.Text.Json 对比 Newtonsoft:缺失功能与完整补救方案
2026/9/17 21:20:15 网站建设 项目流程

.NET 生态里,JSON 序列化是一个绕不开的话题。早期我们几乎都用 Newtonsoft.Json,它功能丰富、API 友好,几乎所有 .NET 项目里都能看到JsonConvert.SerializeObject的身影。后来微软推出了原生 System.Text.Json,性能和内存占用更优,并且成为 ASP.NET Core 默认的 JSON 序列化组件。但很多开发者在迁移时发现,System.Text.Json 虽然够快,却在某些功能上不如 Newtonsoft 顺手,网上相关的对比资料也比较零散。本文会把 System.Text.Json 相对 Newtonsoft 缺失的常用功能梳理清楚,并给出对应的补救方案和完整代码示例,新人能做技术选型参考,老手也能直接在项目中复用。

1. 背景:为什么 System.Text.Json 和 Newtonsoft 会有差异

1.1 Newtonsoft 的历史地位

Newtonsoft.Json 从 .NET 时代早期就开始流行,一度是 .NET 阵营里 JSON 处理的“事实标准”。它的设计理念是“功能尽量完整”,从简单的对象序列化,到复杂的自定义转换器、多态处理、动态对象操作,几乎都能找到对应 API。哪怕到现在,很多老项目的底层还是 Newtonsoft,甚至某些第三方 SDK 也不可避免地依赖它。

Newtonsoft 的优势主要有三个:

  • API 语义直观,JObjectJArray这类 LINQ to JSON 操作非常灵活。
  • 配置项丰富,比如日期格式、null 值处理、循环引用处理、枚举转换等,都有现成配置。
  • 支持大量 .NET Framework 时代的历史项目,社区资料多,踩坑经验也多。

1.2 System.Text.Json 的诞生与优势

System.Text.Json 是微软从 .NET Core 3.0 开始逐步引入的高性能 JSON 库,后续在 .NET 5、.NET 6、.NET 7、.NET 8 中不断增强。它从一开始就把“高性能”和“低内存分配”放在首位,在很多性能敏感场景中,速度明显优于 Newtonsoft。

另外,System.Text.Json 还提供了源生成(Source Generation)模式,可以在编译阶段生成专门的序列化代码,避免运行时反射,这在 AOT 发布和启动速度优化场景中很有价值。

1.3 为什么会有“缺失功能”的感知

严格来说,System.Text.Json 并不是单纯地“少功能”,而是设计理念不同。Newtonsoft 倾向于在运行时通过反射和动态配置解决问题,System.Text.Json 则更强调显式配置、编译期安全和性能上限。于是某些 Newtonsoft 中“开箱即用”的能力,在 System.Text.Json 中可能需要用转换器(Converter)、特性(Attribute)或额外配置来实现。本文把这些差异统一称为“缺失功能”,更多是指“没有直接等价配置”的功能。

2. 环境准备与版本说明

本文示例以 .NET 8 为主,但大部分代码适用于 .NET 6 以上的版本。如果你的项目还在使用 .NET Core 3.1 或 .NET 5,部分 API 可能不存在,需要根据实际版本调整。

  • 操作系统:Windows 10/11、macOS、Linux 均可
  • SDK:.NET 6 或 .NET 8
  • IDE:Visual Studio 2022 或 Rider 或 VS Code
  • 示例类型:控制台项目 + ASP.NET Core Web API
  • 待对比组件:Newtonsoft.Json(推荐 13.0.x 版本)

如果你还没有项目,可以先用命令创建控制台工程:

dotnet new console -n JsonComparisonDemo cd JsonComparisonDemo

为了让同一个项目里同时引用 System.Text.Json 和 Newtonsoft.Json,可以在项目文件里添加 Newtonsoft 包:

dotnet add package Newtonsoft.Json

注意,System.Text.Json 在 .NET Core 3.0+ 中已经内置,无需额外引入,除非你的目标是 .NET Framework 或旧版本。

3. System.Text.Json 与 Newtonsoft 的核心差异盘点

这一节先做整体对比,后续章节再演示具体补救方式。

下表是高频使用场景中两者行为的对比:

功能场景Newtonsoft.JsonSystem.Text.Json
默认属性大小写默认区分大小写,可通过 settings 开启忽略默认区分大小写,需配置PropertyNameCaseInsensitive
null 属性处理默认输出,可配置NullValueHandling.Ignore默认输出,可配置JsonIgnoreCondition.WhenWritingNull
默认值处理可配置DefaultValueHandling.Ignore可配置JsonIgnoreCondition.WhenWritingDefault
DateTime 自定义格式通过DateFormatString直接配置无直接配置,需要自定义 Converter
循环引用默认报错,可配置ReferenceLoopHandling.Ignore默认直接抛异常,需配置ReferenceHandler.IgnoreCycles
多态序列化通过TypeNameHandling或自定义转换器通过[JsonDerivedType]JsonPolymorphic特性
条件序列化(ShouldSerializeXxx)支持较方便无直接等价,需自定义转换器
自定义属性名策略ContractResolver可自由扩展通过PropertyNamingPolicyJsonNamingPolicy实现常见策略
枚举字符串转换StringEnumConverterJsonStringEnumConverter
动态 JSON 操作JObject/JArray非常灵活使用JsonNode/JsonObject/JsonArray
忽略特定属性[JsonIgnore][JsonIgnore]
新增加属性处理MissingMemberHandling可控制UnmappedMemberHandling(.NET 8+)

从表格可见,System.Text.Json 并不是完全不具备这些能力,而是入口不同。下面我们按功能维度拆开讲。

4. 常用缺失功能与补救方案

4.1 自定义 DateTime 日期时间格式

4.1.1 Newtonsoft 的简单方案

Newtonsoft 中,要把 DateTime 序列化为yyyy-MM-dd HH:mm:ss格式,只需设置DateFormatString

var settings = new JsonSerializerSettings { DateFormatString = "yyyy-MM-dd HH:mm:ss" }; var json = JsonConvert.SerializeObject(new { CreateTime = DateTime.Now }, settings); Console.WriteLine(json);

输出:

{"CreateTime":"2025-01-15 14:30:00"}
4.1.2 System.Text.Json 的补救方案

System.Text.Json 默认只支持 ISO 8601 格式,DateTime序列化结果形如2025-01-15T14:30:00。如果前端喜欢用自定义格式,最常见的做法是写一个自定义JsonConverter<DateTime>

using System.Globalization; using System.Text.Json; using System.Text.Json.Serialization; public class DateTimeConverter : JsonConverter<DateTime> { private readonly string _format; public DateTimeConverter(string format = "yyyy-MM-dd HH:mm:ss") { _format = format; } public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.String) { if (DateTime.TryParseExact(reader.GetString(), _format, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date)) { return date; } } // 回退到默认解析 return reader.GetDateTime(); } public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { writer.WriteStringValue(value.ToString(_format, CultureInfo.InvariantCulture)); } }

使用时可以手动注册:

var options = new JsonSerializerOptions { Converters = { new DateTimeConverter("yyyy-MM-dd HH:mm:ss") } }; var json = JsonSerializer.Serialize(new { CreateTime = DateTime.Now }, options); Console.WriteLine(json);

在 ASP.NET Core 中,可以在AddJsonOptions里全局注册:

builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new DateTimeConverter("yyyy-MM-dd HH:mm:ss")); });

这里需要注意:如果前端反传日期,也必须使用相同格式,或者你的 Converter 里要做好兼容解析,否则可能抛出JsonException。实际项目里更稳妥的做法是允许传入 ISO 格式和自定义格式,或者前后端统一标准。

4.2 属性名称大小写不敏感

4.2.1 问题表现

在 System.Text.Json 中,反序列化时默认严格匹配属性名大小写。例如 JSON 里是"UserName",而 C# 属性是UserName,这是没有问题的;但如果 JSON 里是"username",默认情况下就匹配不上。

4.2.2 补救方案

配置PropertyNameCaseInsensitive = true,或者使用PropertyNamingPolicy处理驼峰命名。

var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; var user = JsonSerializer.Deserialize<User>(json, options);

在 ASP.NET Core 中,默认已经把 JSON 属性名按 CamelCase 处理了,但反序列化是否忽略大小写取决于配置。如果接口需要兼容多种命名风格,建议开启:

builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; });

需要注意的是,虽然开启PropertyNameCaseInsensitive会影响性能,但绝大多数业务系统感知不到差异,不用过度担心。

4.3 忽略 null 属性和默认值属性

4.3.1 需求背景

很多接口为了减少无效字段,会要求序列化时跳过值为 null 的字段,或者跳过值为默认值的字段(比如 int 为 0,bool 为 false)。Newtonsoft 里直接配置:

var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore };

System.Text.Json 中,需要在JsonSerializerOptions里设置DefaultIgnoreCondition

using System.Text.Json; using System.Text.Json.Serialization; var options = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; var json = JsonSerializer.Serialize(new { Name = "张三", Remark = (string?)null }, options); Console.WriteLine(json);

输出中Remark字段会被忽略:

{"Name":"张三"}

如果还要忽略默认值:

var options = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault };

这里需要明确:WhenWritingDefault会同时忽略 null 和值类型的默认值,比如int0boolfalse。如果只想忽略引用类型的 null,用WhenWritingNull

另一个常见需求是针对单个属性的忽略,可以使用[JsonIgnore],这一点两个库的行为一致。但 System.Text.Json 还支持带条件的特性写法:

public class User { public string Name { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Phone { get; set; } }

这种方式对某些特定字段的忽略非常方便,不需要改全局配置。

4.4 循环引用处理

4.4.1 问题场景

当对象存在互相引用时,比如订单里有用户,用户里又有订单列表,默认情况下序列化会陷入无限递归。Newtonsoft 默认也会抛异常,但可以通过ReferenceLoopHandling.Ignore直接忽略循环引用:

var settings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };

System.Text.Json 默认遇到循环引用会抛JsonException,提示遇到了循环引用。从 .NET 5 开始,可以通过ReferenceHandler来处理。

方案一:忽略循环引用

var options = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.IgnoreCycles }; var json = JsonSerializer.Serialize(order, options);

方案二:保留引用关系

如果你需要保留对象引用结构,而不是简单忽略,可以使用ReferenceHandler.Preserve,这样会在 JSON 中写入$id$ref等元数据,但往往不是常规接口想要的格式。大多数业务接口只需要IgnoreCycles

注意:ReferenceHandler.IgnoreCycles在遇到循环时会把第二次出现的对象序列化为null,而不是直接报错。这与 Newtonsoft 的ReferenceLoopHandling.Ignore行为不完全相同,但通常能满足“不报错”的需求。

4.5 多态序列化

4.5.1 为什么需要多态

当你的接口返回类型是基类,但实际数据是子类时,反序列化时如果不做特殊处理,得到的对象将丢失子类属性。比如Animal基类下有DogCat子类:

public class Animal { public string Name { get; set; } } public class Dog : Animal { public bool CanBark { get; set; } } public class Cat : Animal { public bool CanCatchMouse { get; set; } }

如果直接序列化Animal列表,子类特有属性会被丢弃;反序列化时也会退化成基类。

4.5.2 Newtonsoft 的做法

Newtonsoft 可以在序列化时写入$type字段,反序列化时根据类型信息还原子类。通常使用TypeNameHandling

var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };

但这种做法有一定安全隐患,如果反序列化来源不可信,可能造成类型混淆攻击。所以在新系统中,我不会建议直接使用TypeNameHandling.All

4.5.3 System.Text.Json 的推荐做法

从 .NET 7 开始,System.Text.Json 提供了更安全的基于特性的多态支持:

[JsonDerivedType(typeof(Dog), typeDiscriminator: "dog")] [JsonDerivedType(typeof(Cat), typeDiscriminator: "cat")] public class Animal { public string Name { get; set; } }

序列化时,会写入一个$type判别字段:

{ "$type": "dog", "Name": "旺财", "CanBark": true }

反序列化时也需要设置JsonSerializerOptions

var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; var json = "{\"$type\":\"dog\",\"Name\":\"旺财\",\"CanBark\":true}"; var animal = JsonSerializer.Deserialize<Animal>(json, options);

这样得到的对象就能正确还原成Dog。这种方式比 Newtonsoft 更安全,因为类型判别值由开发者显式指定,不会默认携带程序集名和类型名。如果你的项目使用 .NET 6 及以下版本,则只能通过自定义转换器实现多态,代码会复杂一些。

4.6 条件序列化:替代 ShouldSerializeXxx

4.6.1 Newtonsoft 的 ShouldSerialize 机制

Newtonsoft 支持通过定义ShouldSerializeXxx()方法动态控制属性是否参与序列化:

public class Person { public string Name { get; set; } public string? Secret { get; set; } public bool ShouldSerializeSecret() { return !string.IsNullOrEmpty(Secret); } }

Secret为 null 或空字符串时,该属性会被忽略。这种机制在处理“根据运行状态决定某个字段是否输出”时非常方便。

4.6.2 System.Text.Json 的替代方案

System.Text.Json 没有直接等价物,但有几种替代思路:

思路一:统一使用JsonIgnore条件,组合JsonIgnoreCondition,适合简单的 null/默认值判断。

思路二:使用自定义转换器实现更复杂的条件判断。比如我们需要一个属性在特定情况下忽略,可以包一层OptionalProperty<T>类型,或者直接在转换器里控制输出结构。

下面用一个自定义转换器的简单思路说明:

public class Person { public string Name { get; set; } [JsonIgnore] public string? Secret { get; set; } public bool ShouldSerializeSecret => !string.IsNullOrEmpty(Secret); }

然后写一个自定义转换器,在Write时手动判断:

public class PersonConverter : JsonConverter<Person> { public override Person Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return JsonSerializer.Deserialize<Person>(ref reader, options); } public override void Write(Utf8JsonWriter writer, Person value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteString("name", value.Name); if (!string.IsNullOrEmpty(value.Secret)) { writer.WriteString("secret", value.Secret); } writer.WriteEndObject(); } }

然后在注册转换器时使用:

var options = new JsonSerializerOptions { Converters = { new PersonConverter() } };

这种方式比较繁琐,但逻辑很直观。实际项目中也可以用 DTO(数据传输对象)提前做映射,只输出需要的字段,比自定义转换器更容易维护。对于只读输出场景,我更推荐使用 DTO,而不是把所有条件判断堆积在序列化层。

4.7 枚举字符串转换

4.7.1 Newtonsoft 的 StringEnumConverter

Newtonsoft 中可以使用:

var settings = new JsonSerializerSettings { Converters = { new StringEnumConverter() } };

也可以给属性添加[JsonConverter(typeof(StringEnumConverter))]

4.7.2 System.Text.Json 的 JsonStringEnumConverter

System.Text.Json 内置了JsonStringEnumConverter

var options = new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } };

如果希望枚举值为驼峰命名风格的字符串,比如"FirstItem"变成"firstItem",从 .NET 8 开始可以传入命名策略:

var options = new JsonSerializerOptions { Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) } };

在 ASP.NET Core 中可以全局注册。需要注意,JsonStringEnumConverter默认会匹配枚举的[EnumMember]特性,使用时可以先验证一下预期输出,避免前后端字段定义不一致。

4.8 动态 JSON 操作:JObject 与 JsonNode 的对比

4.8.1 Newtonsoft 的 JObject

Newtonsoft 的JObject非常强大,可以直接读取、修改和遍历 JSON 数据:

using Newtonsoft.Json.Linq; var json = "{\"name\":\"张三\",\"age\":20}"; var obj = JObject.Parse(json); obj["age"] = 21; obj["city"] = "北京"; Console.WriteLine(obj.ToString());

这种“拿到 JSON 当成文档对象操作”的体验,在很多配置解析、接口转发场景中特别好用。

4.8.2 System.Text.Json 的 JsonNode

从 .NET 6 开始,System.Text.Json 提供了JsonNodeJsonObjectJsonArrayJsonValue等一系列类型。基本用法:

using System.Text.Json.Nodes; var json = "{\"name\":\"张三\",\"age\":20}"; var node = JsonNode.Parse(json); node["age"] = 21; node["city"] = "北京"; Console.WriteLine(node.ToJsonString());

从 API 形态上看,JsonNodeJObject已经非常接近了。不过两者在索引器语义、类型转换、以及对 LINQ 的集成上仍有差异。如果你是重度 JSON 文档操作场景,Newtonsoft 的JObject可能依然更顺手;如果只是偶尔读几个字段,JsonNode足够用。

5. 完整实战:改造一个包含多种序列化场景的 ASP.NET Core Web API

这一节用一个 Web API 示例把前面提到的多个方案串起来,包括全局配置、自定义 Converter、多态支持、忽略 null 和属性命名策略。

5.1 创建项目结构

dotnet new webapi -n SerializationDemo cd SerializationDemo

如果模板创建时包含示例天气接口,可以先删除无关文件,保留基本结构。

5.2 定义模型

Models文件夹中添加两个文件。

Models/User.cs

using System.Text.Json.Serialization; namespace SerializationDemo.Models; public class User { public int Id { get; set; } public string Name { get; set; } = string.Empty; [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Email { get; set; } public UserType Type { get; set; } public DateTime CreateTime { get; set; } } public enum UserType { Normal = 0, Vip = 1, Admin = 2 }

Models/Animal.cs

using System.Text.Json.Serialization; namespace SerializationDemo.Models; [JsonDerivedType(typeof(Dog), "dog")] [JsonDerivedType(typeof(Cat), "cat")] public class Animal { public string Name { get; set; } = string.Empty; } public class Dog : Animal { public bool CanBark { get; set; } } public class Cat : Animal { public bool CanCatchMouse { get; set; } }

5.3 编写转换器

Converters文件夹中添加日期转换器。

Converters/DateTimeConverter.cs

using System.Globalization; using System.Text.Json; using System.Text.Json.Serialization; namespace SerializationDemo.Converters; public class DateTimeConverter : JsonConverter<DateTime> { private readonly string _format; public DateTimeConverter(string format = "yyyy-MM-dd HH:mm:ss") { _format = format; } public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.String && DateTime.TryParseExact(reader.GetString(), _format, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date)) { return date; } return reader.GetDateTime(); } public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { writer.WriteStringValue(value.ToString(_format, CultureInfo.InvariantCulture)); } }

5.4 配置全局序列化选项

Program.cs中配置 MVC 的 JSON 选项:

using System.Text.Json; using System.Text.Json.Serialization; using SerializationDemo.Converters; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; options.JsonSerializerOptions.Converters.Add(new DateTimeConverter("yyyy-MM-dd HH:mm:ss")); options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); }); var app = builder.Build(); app.MapControllers(); app.Run();

关键配置说明:

  • PropertyNamingPolicy = JsonNamingPolicy.CamelCase:输出属性名为驼峰命名。
  • PropertyNameCaseInsensitive = true:反序列化时不区分大小写。
  • DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull:全局忽略 null 字段。
  • 注册DateTimeConverter:统一输出自定义日期格式。
  • 注册JsonStringEnumConverter:枚举输出为字符串。

5.5 编写测试接口

Controllers/TestController.cs

using Microsoft.AspNetCore.Mvc; using SerializationDemo.Models; namespace SerializationDemo.Controllers; [ApiController] [Route("api/test")] public class TestController : ControllerBase { [HttpPost("user")] public IActionResult GetUser() { var user = new User { Id = 1, Name = "张三", Email = null, Type = UserType.Vip, CreateTime = DateTime.Now }; return Ok(user); } [HttpPost("user/input")] public IActionResult InputUser(User user) { return Ok(new { user.Id, user.Name, user.Type, Message = "接收成功" }); } [HttpGet("animal")] public IActionResult GetAnimal() { Animal dog = new Dog { Name = "旺财", CanBark = true }; return Ok(dog); } }

5.6 运行与验证

启动项目:

dotnet run

用 curl 或 Postman 请求/api/test/user,预期输出类似:

{ "id": 1, "name": "张三", "email": null }

因为配置了忽略 null,email字段应该不会出现。type会输出为字符串"Vip"或数字,取决于你注册的枚举转换器。由于我们在Program.cs中注册了JsonStringEnumConvertertype会显示为"Vip"

再请求/api/test/animal

{ "$type": "dog", "name": "旺财", "canBark": true }

可以看到$type字段会被写入,并且子类属性canBark能正常输出。

请求/api/test/user/input时,传入 JSON:

{ "ID": 2, "NAME": "李四", "TYPE": "Admin" }

因为开启了PropertyNameCaseInsensitive,即使大写属性名也能正确绑定。枚举"Admin"也能被正确解析为UserType.Admin

6. 常见问题与排查思路

问题现象常见原因解决思路
反序列化后对象属性全是默认值属性名大小写不匹配或被[JsonIgnore]忽略开启PropertyNameCaseInsensitive,检查属性和 JSON 字段名
DateTime 格式变成 ISO 格式System.Text.Json 默认为 ISO 8601自定义JsonConverter<DateTime>
输出中出现大量 null 字段未配置忽略 null设置DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
遇到循环引用抛异常对象互相引用设置ReferenceHandler.IgnoreCycles,或者改用 DTO
枚举变成数字而不是字符串未注册枚举转换器注册JsonStringEnumConverter
子类属性丢失未配置多态相关特性使用[JsonDerivedType]或在自定义转换器中处理
反序列化 JSON 时报JsonException自定义 Converter 对格式识别失败Read方法中增加容错处理,增加Try-Catch和回退逻辑
迁移后接口字段命名变化PropertyNamingPolicy不同统一配置JsonNamingPolicy.CamelCase,或使用[JsonPropertyName]

如果你在迁移过程中发现输出结果与 Newtonsoft 不一致,建议先做一轮“对比测试”,用同一组数据分别用两个库序列化,再逐一检查差异。很多接口兼容性问题都能通过这种方式快速发现。

7. 最佳实践与工程建议

7.1 选择原则:什么时候继续用 Newtonsoft

如果项目满足以下条件之一,不建议强行迁移到 System.Text.Json:

  • 项目深度使用了JObjectJArrayJToken等动态 JSON API,并且代码量很大。
  • 依赖了 Newtonsoft 的复杂ContractResolver自定义逻辑,例如根据运行时类型动态决定序列化字段。
  • 第三方库或旧框架强依赖 Newtonsoft,存在程序集绑定冲突。
  • 项目是从 .NET Framework 迁移到 .NET Core 的存量系统,当前运行稳定,迁移收益不大。

7.2 什么时候考虑 System.Text.Json

  • 项目是全新开发的 .NET 6+ 应用。
  • 对性能、内存占用比较敏感。
  • 希望使用源生成(Source Generation)减少反射开销,甚至为 AOT 发布做准备。
  • 期望减少第三方依赖,让项目更“原生”。

7.3 平滑迁移策略

如果决定从 Newtonsoft 迁移到 System.Text.Json,建议分几步走:

第一步:统一项目中的 JSON 序列化入口,不要散落各处直接调用JsonConvert

第二步:在测试环境中写一个“双序列化对比工具”,输入同样对象,分别使用 Newtonsoft 和 System.Text.Json 序列化,比较 JSON 文本差异,确保字段名、格式、忽略规则一致。

第三步:为特殊的类型编写自定义 Converter,优先保证功能兼容,再做性能优化。

第四步:调整前端接口调用,如果字段名或格式发生变化,需要同步通知前端团队。

7.4 序列化配置规范

实际项目中,建议把序列化配置集中管理,而不是在每次调用时临时创建JsonSerializerOptions。每次创建JsonSerializerOptions都有额外开销,而且容易遗漏配置项。

常见做法是定义一个静态配置类:

using System.Text.Json; using System.Text.Json.Serialization; public static class JsonOptionsProvider { public static JsonSerializerOptions Default { get; } = Create(); private static JsonSerializerOptions Create() { var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, ReferenceHandler = ReferenceHandler.IgnoreCycles, WriteIndented = false }; options.Converters.Add(new JsonStringEnumConverter()); return options; } }

这样项目中只要使用同一个JsonOptionsProvider.Default,就能保证序列化风格一致。

7.5 安全建议

  • 反序列化时永远不要相信外部输入。虽然 System.Text.Json 的[JsonDerivedType]比 Newtonsoft 的TypeNameHandling安全,但不代表可以接受任意类型。
  • 尽量避免使用TypeNameHandling.All处理不受信任的数据。
  • 自定义 Converter 中若有Try-Catch,不要吞掉所有异常,至少要让错误在日志中可见。
  • 生产环境修改序列化配置前,先在测试环境用真实规模数据验证。

8. 总结

System.Text.Json 和 Newtonsoft 不是简单的替代关系。System.Text.Json 在性能和内存消耗上更优,也更符合现代 .NET 发展方向;Newtonsoft 则胜在功能全面和 API 灵活。对于大多数常见场景,System.Text.Json 通过自定义 Converter、特性配置和JsonSerializerOptions都能给出不错的补救方案,只是需要开发者多写一点代码。

如果你正准备做技术选型,可以结合团队熟悉程度和项目实际需求决定。如果项目已经稳定运行在 Newtonsoft 上,不必为了换而换;如果项目还在早期阶段,直接选择 System.Text.Json 并统一配置,后续维护成本会更低。

希望这篇文章能帮你理清两者差异,也欢迎在评论区聊聊你项目中还遇到过哪些 System.Text.Json 的“坑”,我后面可以继续整理对应的解决方案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询