CodeGym /课程 /C# SELF /使用 System.Text.Json...

使用 System.Text.Json 的序列化

C# SELF
第 44 级 , 课程 3
可用

1. 介绍

长期以来,在 .NET 生态里,处理 JSON 的主要工具是很棒的第三方包 Newtonsoft.Json(也叫 Json.NET)。它功能强大、灵活,直到现在仍被广泛使用。但随着 .NET 9 和 C# 14 的到来,Microsoft 觉得应该有一个内置的高性能 JSON 序列化器。于是就有了 System.Text.Json

为什么需要新的方案?System.Text.Json 是为现代场景设计的,解决了多年使用第三方库积累的问题。它针对最高性能和安全性做了优化,适合异步场景和 web-API,而且 — 最棒的是 — 不需要通过 NuGet 安装:平台里自带。

当然,Newtonsoft.Json 还在,并且我们会在后面提到它。但对大多数新项目来说,System.Text.Json 是默认选择。准备好吧,现在我们要教对象说 JSON 语言了!

2. 使用 System.Text.Json 的基础

简单的对象序列化

好,先从基础魔法开始。把对象序列化成 JSON 字符串。

using System;
using System.Text.Json; // 别忘了加上!

public class Player
{
    public string Name { get; set; }
    public int Health { get; set; }
    public bool IsAlive { get; set; }
}

// 在程序的某处:
Player player = new Player { Name = "Aragorn", Health = 100, IsAlive = true };

// 序列化:
string json = JsonSerializer.Serialize(player);
Console.WriteLine(json); // 输出: {"Name":"Aragorn","Health":100,"IsAlive":true}

备注: 如果你刚开始学序列化,这个例子说明了有多简单:JsonSerializer.Serialize — 就搞定了。

对象反序列化

从 JSON 字符串把对象“复活”出来:

string incomingJson = "{\"Name\":\"Legolas\",\"Health\":88,\"IsAlive\":true}";

Player player2 = JsonSerializer.Deserialize<Player>(incomingJson);
Console.WriteLine(player2.Name);   // Legolas
Console.WriteLine(player2.Health); // 88
Console.WriteLine(player2.IsAlive); // true

备注: 如果 JSON 的结构和你的类一致 — 一切都会像钟表一样工作。否则可能会抛异常或得到默认值。

3. 序列化的工作原理和机制

如何进行匹配

System.Text.Json 默认使用类里相同的属性名。区分大小写!如果 JSON 里写了 health 而不是 Health,反序列化就不会匹配 — 属性会保持默认值(0falsenull)。

例如:

// 小写键的 JSON:
string badJson = "{\"name\":\"Gimli\",\"health\":120,\"isAlive\":true}";
Player player3 = JsonSerializer.Deserialize<Player>(badJson);
Console.WriteLine(player3.Name);    // 空
Console.WriteLine(player3.Health);  // 0
Console.WriteLine(player3.IsAlive); // false

有趣的事实: 许多 API 使用 camelCase(比如 health),而在 C# 里通常用 PascalCase(比如 Health)。这可以通过下面的设置来解决。

4. 控制序列化 — 选项和配置

格式化 JSON:可读的输出

有时候你想要不是紧凑的,而是美观的 JSON — 用于配置或日志。

var options = new JsonSerializerOptions
{
    WriteIndented = true // 添加缩进
};

string prettyJson = JsonSerializer.Serialize(player, options);
Console.WriteLine(prettyJson);
/*
{
  "Name": "Frodo",
  "Health": 50,
  "IsAlive": true,
  "Inventory": [
    "Ring",
    "Bread",
    "Torch"
  ],
  "Position": {
    "X": 5,
    "Y": 15
  }
}
*/

通过设置 WriteIndented,序列化器会加入缩进和换行。

控制命名风格(CamelCase vs PascalCase)

如果你在做的 web-API 全部键都是 camelCase,可以开启命名策略:

var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

string camelCaseJson = JsonSerializer.Serialize(player, options);
// {"name":"Frodo","health":50,"isAlive":true,"inventory":["Ring","Bread","Torch"],"position":{"x":5,"y":15}}

这样在反序列化时这些键就能正确匹配:

string apiJson = "{\"name\":\"Bilbo\",\"health\":40,\"isAlive\":true,\"inventory\":[\"Mug\"],\"position\":{\"x\":10,\"y\":5}}";
Player bilbo = JsonSerializer.Deserialize<Player>(apiJson, options);
Console.WriteLine(bilbo.Name); // Bilbo

5. 有用的细节

使用属性 [JsonIgnore]

有时候不想序列化所有属性 — 比如私密数据或临时计算值。

using System.Text.Json.Serialization;

public class Player
{
    public string Name { get; set; }
    public int Health { get; set; }

    [JsonIgnore] // 这个属性不会出现在 JSON 里
    public bool IsSecretCharacter { get; set; }
}

现在序列化时:

var player = new Player { Name = "Boromir", Health = 80, IsSecretCharacter = true };
string json = JsonSerializer.Serialize(player);
Console.WriteLine(json); // {"Name":"Boromir","Health":80}

反序列化时 IsSecretCharacter 会得到默认值(false)。

使用 [JsonPropertyName("...")]

假设代码里属性叫 IsAlive,但在 JSON 里需要是 "status"

using System.Text.Json.Serialization;

public class Player
{
    public string Name { get; set; }
    public int Health { get; set; }

    [JsonPropertyName("status")]
    public bool IsAlive { get; set; }
}

序列化后的样子:

var player = new Player { Name = "Pippin", Health = 60, IsAlive = false };
string json = JsonSerializer.Serialize(player);
Console.WriteLine(json); // {"Name":"Pippin","Health":60,"status":false}

反序列化时,键 "status" 会正确填充属性 IsAlive

内置的限制和安全特性

  • 默认只序列化带有 public getter/setter 的公共属性;私有字段/属性会被忽略。
  • 遇到循环引用会抛出异常: "A possible object cycle was detected"
  • Newtonsoft.Json 不同,标准序列化器更少依赖对类型的“魔术”处理 — 但在典型场景下更安全也更快。

6. 常见错误和坑

你不小心改了 JSON 里的属性名但忘了更新代码 — 结果属性得到默认值(null0false)。

JSON 缺少必需字段 — 对应的对象属性会是默认值(参见 文档)。

属性没有 public setter — 反序列化时它不会被填充。

更改了嵌套类或集合的结构 — 反序列化可能会失败或产生意外结果。

在不同嵌套层级出现相同名字(父对象和子对象里)会让人困惑,调试也难。

有时问题跟平台版本有关:旧版本的 System.Text.Json 在处理某些类型(比如 DictionaryDateTimeenum)时表现不佳,但在 .NET 7/8/9 中很多问题已经修复。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION