【C#】 JSON 序列化与反序列化:从入门到最佳实践

在现代软件开发中,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 序列化看似只是"对象转字符串"的简单操作,但其背后涉及的类型系统、内存管理和安全考量,正是工程化思维的最佳体现。

相关推荐
胖纸不争7 小时前
自建 Copilot Cli 代理:让 GitHub Copilot 真正"Bring Your Own Key"
ai·c#
li星野8 小时前
FastAPI 响应类型完全指南:从 JSON 到流式响应、异常处理与输出模型
前端·json·fastapi
FuckPatience9 小时前
C# new List<T>(IEnumerable<T> collection),链表初始化时传入已存在链表
链表·c#·list
专注VB编程开发20年11 小时前
工控上位机开发为什么固死.net 4.5.2sdk?适配win7
python·信息可视化·c#
狂人开飞机13 小时前
18. 中介者模式(Mediator Pattern)
设计模式·c#·中介者模式
victory_li13 小时前
OpenVINO + Yolov26 + C# + .net framework4.8实现分类推理
yolo·c#·openvino
吴爃13 小时前
Logstash WebHDFS 异常导致历史日志补读与 OOM
c#·linq
就叫飞六吧13 小时前
JSON 与 JSON Schema:从“数据快递”到“使用说明书”
json
WarPigs13 小时前
C# EntityFramework笔记
数据库·c#