CodeGym /課程 /C# SELF /使用 Newtonsoft.Json

使用 Newtonsoft.Json

C# SELF
等級 44 , 課堂 4
開放

1. 介紹

Newtonsoft.Json 在某種意義上是個「老牌但可靠」的工具。多年前它基本上就是 .NET 生態裡處理 JSON 的事實標準,早在 System.Text.Json 出現之前就廣泛被採用。數百萬個專案、成千上萬的函式庫與 framework(甚至 ASP.NET Core 的很多地方直到最近還在用)都用過 Json.NET。

它的優點包括:

  • 功能豐富且彈性高:Json.NET 提供大量設定、屬性和自訂化選項,能細緻控制序列化/反序列化流程,能做很多 System.Text.Json 要麼做起來麻煩、要麼根本做不到的事。
  • 對「不完美」的 JSON 友善:從外部系統來的資料不見得很標準 — Json.NET 通常比較寬容,能少一點痛苦地把資料反序列化回來。
  • 向後相容:如果某個專案或函式庫依賴 Json.NET,會用到它的能力就還是必要的技能。

當然,System.Text.Json 在效能上通常比較快,也是為現代需求設計。但如果你需要特殊邏輯或極高的彈性,Newtonsoft.Json 仍然很有用。

怎麼安裝 Newtonsoft.Json

因為它是第三方函式庫,所以用 NuGet 加套件:

  1. 打開你的專案。
  2. 在 Solution Explorer 右鍵專案。
  3. 選「Manage NuGet Packages...」。
  4. 搜尋 Newtonsoft.Json
  5. 選擇套件然後按「Install」。

安裝完就會有新的相依,可以開始用 Json.NET。

Newtonsoft.Json 和 System.Text.Json 能力比較

功能 System.Text.Json Newtonsoft.Json
簡單序列化/反序列化
屬性標註支援 部分支援 完整支援
自訂 converters
對私有欄位的處理
處理動態結構 有限支援
LINQ to JSON (JObject/JArray)
Reference Loop Handling
支援 DataTable、DataSet 與複雜型別
效能 較好 良好

2. 簡單物件的序列化與反序列化範例

拿一個簡單的遊戲資料結構,課程中會逐步擴充:

public class Player
{
    public string Name { get; set; }
    public int Health { get; set; }
    public bool IsAlive { get; set; }
    public List<string> Inventory { get; set; }
    public Position Position { get; set; }
}

public class Position
{
    public int X { get; set; }
    public int Y { get; set; }
}

現在把 Player 物件存成 JSON,然後再還原回來:

using Newtonsoft.Json;

Player player1 = new Player
{
    Name = "Aragorn",
    Health = 100,
    IsAlive = true,
    Inventory = new List<string> { "sword", "bow", "healing potion" },
    Position = new Position { X = 10, Y = 25 }
};

// 序列化為 JSON 字串
string json = JsonConvert.SerializeObject(player1, Formatting.Indented);
Console.WriteLine(json);

// 反序列化回 Player 物件
Player player2 = JsonConvert.DeserializeObject<Player>(json);
Console.WriteLine($"名字: {player2.Name}, 生命值: {player2.Health}");

就這樣?差不多,但這只是冰山一角。我們再看哪些地方讓 Newtonsoft.Json 更靈活、好用。

3. 格式化、設定與進階選項

序列化時你可以得到緊湊的一行字串,或是漂亮縮排的 JSON,行為由參數與設定控制。

範例:不同的格式呈現

// 好閱讀的 JSON
string prettyJson = JsonConvert.SerializeObject(player1, Formatting.Indented);

// 精簡(minified)的 JSON
string compactJson = JsonConvert.SerializeObject(player1, Formatting.None);

序列化設定

需要更細的控制時,可以傳入 JsonSerializerSettings

var settings = new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore, // 跳過值為 null 的欄位
    DefaultValueHandling = DefaultValueHandling.Ignore, // 跳過預設值欄位
    Formatting = Formatting.Indented
};
string customJson = JsonConvert.SerializeObject(player1, settings);

你可以完全掌控輸出格式:想跳過空欄位就行,想連私有屬性也序列化也可以,用 contract 調整。

4. 用屬性控制欄位與屬性

Newtonsoft.Json 支援一套強大的屬性系統,可以直接在 class 層級控制序列化行為。

JsonProperty — 改名屬性

當 JSON 協定需要其他欄位名稱時:

public class Player
{
    [JsonProperty("player_name")]
    public string Name { get; set; }
    // ...
}

結果 JSON 會長得像:

{ "player_name": "Aragorn", ... }

JsonIgnore — 忽略屬性

public class Player
{
    [JsonIgnore]
    public int Health { get; set; }
}

現在屬性 Health 在序列化時會被移除。

JsonConverter — 自訂轉換邏輯

可以指定某個欄位用哪個 converter 來處理:

public class Player
{
    [JsonConverter(typeof(InventoryToStringConverter))]
    public List<string> Inventory { get; set; }
}

(關於 converters 的細節在下面會講)

5. 處理巢狀物件與集合

Json.NET 對巢狀物件、陣列、集合、字典處理得很好。

範例:字典

public class GameStats
{
    public Dictionary<string, int> Scores { get; set; }
}

GameStats stats = new GameStats
{
    Scores = new Dictionary<string, int>
    {
        ["Alice"] = 1023,
        ["Bob"] = 999
    }
};

string statsJson = JsonConvert.SerializeObject(stats, Formatting.Indented);
Console.WriteLine(statsJson);

產生的 JSON 如下:

{
  "Scores": {
    "Alice": 1023,
    "Bob": 999
  }
}

6. 複雜結構:循環引用與 Self-Referencing Objects

有時候物件彼此互相參照,Newtonsoft.Json 可以透過特定設定處理這種結構。

範例:處理循環引用

public class Person
{
    public string Name { get; set; }
    public Person Parent { get; set; }
    public List<Person> Children { get; set; }
}

// 為循環引用設定序列化
var settings = new JsonSerializerSettings
{
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore // 或者 .Serialize
};

Person p1 = new Person { Name = "爸爸" };
Person p2 = new Person { Name = "兒子", Parent = p1 };
p1.Children = new List<Person> { p2 };

string json = JsonConvert.SerializeObject(p1, settings);
Console.WriteLine(json);

預設如果把 ReferenceLoopHandling = Error 留著,你會看到例外。這個保護可以避免無限序列化。

7. 處理動態結構:JObject, JArray

當 JSON 結構事先不知道或會動態改變時,可以用不強型別的動態方式處理,不必先寫一堆 POCO 類別。

主要型別:

  • JObject — 表示 JSON 物件。
  • JArray — 表示陣列。
using Newtonsoft.Json.Linq;

// 將字串轉為 JObject
string json = @"{ 'name': 'Aragorn', 'health': 100 }";
JObject obj = JObject.Parse(json);

Console.WriteLine((string)obj["name"]); // Aragorn
Console.WriteLine((int)obj["health"]);  // 100

// 動態新增屬性
obj["class"] = "Ranger";
Console.WriteLine(obj.ToString());

對陣列做迭代

string jsonArr = @"['apple', 'banana', 'cherry']";
JArray array = JArray.Parse(jsonArr);

foreach (JToken item in array)
{
    Console.WriteLine(item);
}

8. 支援版本控制與必填欄位

JSON 格式會演進,欄位可能會消失或新增。可以用屬性與設定來處理這些情況:

  • [JsonProperty(Required = Required.Always)] — 要求欄位必須存在(否則拋出例外)。
  • [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] — 欄位不存在時套用預設值。
public class Player
{
    [JsonProperty(Required = Required.Always)]
    public string Name { get; set; }

    [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
    [DefaultValue(50)]
    public int Health { get; set; }
}

9. 日期與時間格式處理

處理日期時常常需要指定明確的格式。

var dateSettings = new JsonSerializerSettings
{
    DateFormatString = "yyyy-MM-dd"
};
string json = JsonConvert.SerializeObject(DateTime.Now, dateSettings);
Console.WriteLine(json); // "2024-06-15"

反序列化的範例:

string dateJson = "\"2024-06-15\""; // 注意:這是一個帶引號的字串!
DateTime dt = JsonConvert.DeserializeObject<DateTime>(dateJson);
Console.WriteLine(dt);
1
問卷/小測驗
標準序列化類別,等級 44,課堂 4
未開放
標準序列化類別
用於序列化的類別與函式庫
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION