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,反序列化就不會成功 — 該屬性會保留預設值(0、false 或 null)。
例如:
// 使用小寫的 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 裡的屬性名稱但忘了更新程式碼 — 結果屬性會拿到預設值(null、0、false)。
JSON 裡缺少需要的欄位 — 對應的物件屬性會是預設值(參考 文件)。
屬性沒有公開的 setter — 在反序列化時不會被填充。
改變了內嵌類別或集合的結構 — 反序列化可能會失效或出現意外結果。
在不同的巢狀層級使用相同名稱(父物件和子物件都有相同 key)會造成混淆、增加除錯難度。
有時候問題出在平台版本:舊版的 System.Text.Json 對某些型別支援較差(例如 Dictionary、DateTime、enum),但在 .NET 7/8/9 中很多已經被修正。
GO TO FULL VERSION