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 key:
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)

如果你在跟一個所有 key 都是 camelCase 的 web-API 互動,可以啟用命名策略:

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}}

這樣在反序列化時,這類 key 也能正確對應:

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}

反序列化時 key "status" 會正確填到屬性 IsAlive 上。

內建的限制與安全性特點

  • 預設只序列化有公開 getter/setter 的公開屬性;私有欄位/屬性會被忽略。
  • 遇到循環參考會丟出例外:"A possible object cycle was detected"
  • Newtonsoft.Json 不同,標準序列化器較少依賴那種對型別做「魔術」的技巧 — 因此在一般情境下更安全且更快。

6. 常見錯誤與地雷

你不小心改了 JSON 裡的屬性名稱但忘了更新程式碼 — 結果屬性會拿到預設值(null0false)。

JSON 裡缺少需要的欄位 — 對應的物件屬性會是預設值(參考 文件)。

屬性沒有公開的 setter — 在反序列化時不會被填充。

改變了內嵌類別或集合的結構 — 反序列化可能會失效或出現意外結果。

在不同的巢狀層級使用相同名稱(父物件和子物件都有相同 key)會造成混淆、增加除錯難度。

有時候問題出在平台版本:舊版的 System.Text.Json 對某些型別支援較差(例如 DictionaryDateTimeenum),但在 .NET 7/8/9 中很多已經被修正。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION