在现代软件开发中,JSON(JavaScript Object Notation)已成为数据交换的事实标准。无论是与 Web API 通信、配置文件管理,还是微服务间的消息传递,JSON 都无处不在。C# 作为一门强类型语言,提供了丰富而成熟的 JSON 处理能力。本文将系统性地介绍 C# 中 JSON 序列化与反序列化的核心概念、主流方案、设计哲学以及工程实践中的关键考量。
一、序列化与反序列化的本质
在深入技术细节之前,有必要先厘清这两个核心概念:
- 序列化(Serialization) 是指将内存中的对象状态转换为可存储或可传输格式的过程。在 JSON 语境下,就是将 C# 对象图转换为符合 JSON 规范的字符串。
- 反序列化(Deserialization) 则是其逆过程:将 JSON 字符串重新构建为内存中的 C# 对象实例。
这一过程看似简单,实则涉及类型系统映射、引用关系处理、循环依赖检测、数据验证等复杂问题。理解这一点,有助于我们在遇到异常行为时快速定位根因。
二、C# 中的两大主流方案
.NET 生态中,JSON 处理经历了从第三方库到官方内置方案的演进,形成了当前双雄并立的格局。
1. Newtonsoft.Json(Json.NET)
作为 .NET 领域历史最悠久、功能最丰富的 JSON 库,Newtonsoft.Json 由 James Newton-King 开发,长期以来一直是行业标准。其特点包括:
- 极致的灵活性:几乎支持任何复杂的序列化场景,包括自定义转换器、引用保留、类型信息嵌入等高级功能。
- 丰富的配置选项:通过 JsonSerializerSettings 可以精细控制格式化行为、空值处理、日期格式、命名策略等。
- 广泛的兼容性:支持从 .NET Framework 2.0 到现代 .NET 的全版本覆盖,拥有庞大的社区生态和文档积累。
在 .NET Core 3.0 之前,Newtonsoft.Json 几乎是每个项目的标配依赖。
2. System.Text.Json
随着 .NET Core 3.0 的发布,微软推出了官方内置的高性能 JSON 库 System.Text.Json。其设计目标非常明确:
- 性能优先:基于 Span 和 UTF-8 直接处理,避免了大量字符串分配,在吞吐量和内存占用上显著优于 Newtonsoft.Json。
- 原生集成:无需额外 NuGet 包,与 .NET 框架深度整合,特别是在 ASP.NET Core 中作为默认序列化器。
- 安全性:默认启用严格模式,对深度嵌套、数字范围等有内置保护机制。
然而,早期版本的 System.Text.Json 在功能丰富度上不及 Newtonsoft.Json。经过 .NET 5、6、7、8 的持续迭代,目前已覆盖绝大多数常见场景,但在处理高度动态化或遗留系统兼容时,仍可能需要借助 Newtonsoft.Json 的灵活性。
三、核心机制与配置哲学
无论选择哪种方案,理解其配置哲学都是高效使用的前提。
1. 命名策略(Naming Policy)
C# 遵循 PascalCase 命名规范(如 UserName),而 JSON 生态中更常见 camelCase(如 userName)或 snake_case(如 user_name)。序列化库通常提供全局或局部的命名策略配置,以确保两端命名风格的无缝映射。
2. 空值处理
对于可空属性,需要决定序列化时是否包含空值字段。包含空值会增加传输体积,但有助于保持结构完整性;忽略空值则能减少数据量,但可能导致接收方无法区分"未设置"和"设置为 null"。
3. 日期与时间处理
JSON 标准并未定义日期格式,实践中通常采用 ISO 8601 格式(如 2026-05-21T08:50:00Z)。处理时区、本地时间与 UTC 时间的转换,以及高精度时间戳的支持,都是配置中需要关注的要点。
4. 循环引用与对象图
当对象间存在双向引用(如父子节点互相引用)时,简单的序列化会导致无限递归。成熟的库提供两种解决路径:一是忽略循环引用(可能丢失数据),二是通过引用标识符(id、id、id、ref)保留对象图的完整性。
5. 多态类型处理
在面向对象设计中,基类引用指向派生类实例是常见模式。序列化时需要决定是否包含类型信息,以便反序列化时准确重建对象类型。这通常通过类型判别器(Discriminator)或全类型名称嵌入实现。
四、自定义行为与扩展点
实际项目中,自动映射往往无法满足所有需求。两种方案都提供了强大的扩展机制。
1. 自定义转换器(JsonConverter)
当标准映射规则不适用时(如特殊格式字符串、复杂枚举、加密字段),可以编写自定义转换器。转换器允许你完全控制特定类型的读写逻辑,实现业务层面的精确映射。
2. 属性注解(Attributes)
通过在模型属性上添加注解,可以局部覆盖全局配置。常见用途包括:
- 指定 JSON 中的字段名称(如将 C# 的 IPAddress 映射为 JSON 的 ip_address)
- 控制字段的序列化/反序列化可见性
- 设置日期格式、数值精度等
- 标记必填字段或忽略特定属性
3. 契约解析器(Contract Resolver)
在 Newtonsoft.Json 中,契约解析器提供了更底层的控制,允许通过编程方式动态决定序列化行为,而无需修改模型类本身。这在无法注解第三方类或需要运行时动态策略时特别有用。
五、基于Newtonsoft.Json库代码实现
csharp
public class JsonUtils
{
/// <summary>
/// 将对象转换成Json文件
/// </summary>
/// <param name="obj">类对象参数</param>
/// <param name="jsonFile">Json文件完整路径</param>
/// <returns></returns>
public static bool ToJsonFile(object obj, string jsonFile)
{
if (obj != null)
{
string json = JsonConvert.SerializeObject(obj);
File.WriteAllText(jsonFile, json);
return true;
}
return false;
}
/// <summary>
/// 从Json文件反序列化
/// </summary>
/// <typeparam name="T">泛型类</typeparam>
/// <param name="jsonFile">Json文件完整路径</param>
/// <returns></returns>
public static T? FromJsonFile<T>(string jsonFile)
{
if (File.Exists(jsonFile))
{
string json = File.ReadAllText(jsonFile);
T? jsonObj = JsonConvert.DeserializeObject<T>(json);
return jsonObj;
}
return default;
}
/// <summary>
/// 将对象序列化Json字符串
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static string ToJson(object? obj)
{
if (obj != null)
{
string json = JsonConvert.SerializeObject(obj);
return json;
}
return string.Empty;
}
/// <summary>
/// 将Json字符串反序列化为对象
/// </summary>
/// <typeparam name="T">泛型类</typeparam>
/// <param name="json">Json字符串</param>
/// <returns></returns>
public static T? FromJson<T>(string? json)
{
if (!string.IsNullOrEmpty(json))
{
T? jsonObj = JsonConvert.DeserializeObject<T>(json);
return jsonObj;
}
return default;
}
}
六、性能考量与选型建议
性能差异是两种方案最显著的区别之一。System.Text.Json 在设计上充分利用了现代 .NET 的内存管理优化,特别适合高吞吐量场景,如微服务网关、高频 API 接口、实时数据流处理。
然而,性能并非唯一指标。在以下场景,Newtonsoft.Json 仍是更稳妥的选择:
- 需要处理高度动态的数据(如 JObject、JToken 的灵活操作)
- 遗留系统的深度集成,依赖其特有的序列化特性
- 复杂的继承层次结构需要精确的类型信息保留
- 项目中已大量使用其高级功能,迁移成本过高
对于新项目,建议优先评估 System.Text.Json 是否满足需求;若功能缺口不大,其原生集成和性能优势值得采用。若遇到边界情况,两者也可以共存------.NET 允许在不同场景下分别使用不同的序列化器。
七、反序列化的安全与健壮性
反序列化是不可信数据进入系统的关键入口,必须谨慎处理。
1. 类型安全
反序列化到强类型对象是最安全的做法,因为类型系统会自然过滤不兼容数据。若必须使用弱类型(如 JsonElement 或 JToken),务必在访问时进行显式类型检查和转换。
2. 深度与大小限制
恶意构造的 JSON(如深度嵌套对象、超大字符串)可能导致栈溢出或内存耗尽。生产环境应配置最大深度、字符串长度和缓冲区大小限制。
3. 数字范围检查
JSON 数字没有显式类型边界,反序列化到 C# 数值类型时可能发生溢出。应在模型中使用合适范围的类型,或在反序列化后手动验证。
八、总结
C# 的 JSON 处理能力经过十余年发展,已形成成熟且多元的解决方案。System.Text.Json 代表了官方的高性能、现代化方向,而 Newtonsoft.Json 凭借其无与伦比的灵活性仍在复杂场景中占据重要地位。
作为开发者,理解两者的设计哲学差异,根据项目实际需求做出选型,并在序列化契约设计中兼顾性能、安全与可维护性,是构建健壮系统的关键。JSON 序列化看似只是"对象转字符串"的简单操作,但其背后涉及的类型系统、内存管理和安全考量,正是工程化思维的最佳体现。