JSON (JavaScript Object Notation) 已經成為現代 Web 應用和服務之間數據交換的通用語言。無論你是開發后端 API、與第三方服務集成,還是處理配置文件,都繞不開 JSON 的解析與生成。在 C# .NET 世界里,處理 JSON 有多種選擇,其中 Newtonsoft.Json
(又稱 Json.NET)因其強大的功能、靈活性和廣泛的應用,至今仍是許多開發者的首選。
本文將深入探討如何使用 Newtonsoft.Json
在 C# 中解析 JSON 字符串,以及如何利用 HttpClient
結合 Newtonsoft.Json
發送帶有 application/json
請求體的 POST 請求。
準備工作:安裝 Newtonsoft.Json
首先,確保你的項目中已經安裝了 Newtonsoft.Json
NuGet 包。在 Visual Studio 中,可以通過以下步驟安裝:
- 右鍵點擊項目,選擇 “管理 NuGet 程序包…”。
- 在 “瀏覽” 標簽頁搜索 “Newtonsoft.Json”。
- 選擇找到的包,點擊 “安裝”。
或者,在 NuGet 包管理器控制臺運行以下命令:
Install-Package Newtonsoft.Json
第一部分:JSON 解析的藝術 - 將 JSON 轉化為 C# 對象
處理 JSON 字符串最常見也最安全的方式是將其映射到預先定義的 C# 類或結構體。Newtonsoft.Json
提供了強大的反序列化功能,能夠將 JSON 結構自動轉化為對應的 C# 對象。
1. 將 JSON 反序列化為強類型對象 (JsonConvert.DeserializeObject<T>
)
這種方法適用于你知道 JSON 的具體結構,并可以為其定義一個匹配的 C# 類。這是推薦的方式,因為它提供了編譯時類型檢查,減少了運行時錯誤。
假設我們有以下 JSON 數據,代表一個用戶的信息:
{"userName": "Alice","age": 30,"isActive": true,"roles": ["Viewer", "Contributor"],"profile": {"email": "alice@example.com","bio": "Software Engineer"}
}
為了解析它,我們需要定義對應的 C# 類:
using System.Collections.Generic; // 用于 List<string>public class UserProfile
{public string Email { get; set; }public string Bio { get; set; }
}public class User
{// 默認情況下,屬性名需要與 JSON key 匹配(大小寫敏感)public string UserName { get; set; }public int Age { get; set; }public bool IsActive { get; set; }public List<string> Roles { get; set; } // JSON 數組通常映射到 List<T> 或 T[]public UserProfile Profile { get; set; } // 嵌套對象映射到嵌套類
}
然后,使用 JsonConvert.DeserializeObject<T>
方法進行反序列化:
using System;
using Newtonsoft.Json; // 引入 Newtonsoft.Json 命名空間public class JsonParsingExample
{public static void Main(string[] args){string jsonString = @"{""userName"": ""Alice"",""age"": 30,""isActive"": true,""roles"": [""Viewer"", ""Contributor""],""profile"": {""email"": ""alice@example.com"",""bio"": ""Software Engineer""}}";try{// 反序列化 JSON 字符串到 User 對象User user = JsonConvert.DeserializeObject<User>(jsonString);// 訪問反序列化后的對象屬性Console.WriteLine($"User Name: {user.UserName}");Console.WriteLine($"Age: {user.Age}");Console.WriteLine($"Is Active: {user.IsActive}");Console.WriteLine($"Roles: {string.Join(", ", user.Roles)}"); // 遍歷列表Console.WriteLine($"Email: {user.Profile.Email}"); // 訪問嵌套對象屬性Console.WriteLine($"Bio: {user.Profile.Bio}");}catch (JsonReaderException ex){// 處理 JSON 格式錯誤或其他解析異常Console.WriteLine($"JSON 解析錯誤: {ex.Message}");}catch (Exception ex){// 處理其他潛在異常Console.WriteLine($"發生錯誤: {ex.Message}");}}
}
處理 JSON 數組:
如果你的 JSON 根是一個數組,例如 [ {"id": 1, "name": "Item A"}, {"id": 2, "name": "Item B"} ]
,你可以反序列化到一個 List<T>
或 T[]
:
using System.Collections.Generic;public class Item
{public int Id { get; set; }public string Name { get; set; }
}string jsonArrayString = @"
[{ ""id"": 1, ""name"": ""Item A"" },{ ""id"": 2, ""name"": ""Item B"" }
]";List<Item> items = JsonConvert.DeserializeObject<List<Item>>(jsonArrayString);
// 現在 items 是一個包含兩個 Item 對象的列表
自定義屬性名映射 ([JsonProperty]
屬性):
如果你的 C# 屬性名與 JSON key 不一致(例如,C# 使用 PascalCase,JSON 使用 camelCase 或 snake_case),可以使用 [JsonProperty("json_key_name")]
屬性來指定映射關系:
using Newtonsoft.Json;public class Product
{[JsonProperty("product_code")] // JSON 中是 "product_code"public string Code { get; set; } // C# 中是 Code[JsonProperty("itemPrice")] // JSON 中是 "itemPrice"public decimal Price { get; set; } // C# 中是 Price
}
2. 使用 JToken
/JObject
/JArray
進行動態解析
當你面對結構未知、不規范的 JSON,或者只需要訪問/修改 JSON 中的一小部分數據時,將整個 JSON 強制映射到強類型對象可能不太方便。這時,Newtonsoft.Json.Linq
命名空間下的 JToken
、JObject
和 JArray
類提供了靈活的動態訪問能力。
JToken
: 所有 JSON 元素的基類。JObject
: 代表一個 JSON 對象{}
。JArray
: 代表一個 JSON 數組[]
。JValue
: 代表一個具體的 JSON 值(字符串、數字、布爾、null)。
使用 JObject.Parse()
或 JArray.Parse()
來解析 JSON 字符串到這些動態類型:
using System;
using Newtonsoft.Json.Linq; // 引入 Newtonsoft.Json.Linqpublic class JsonDynamicParsingExample
{public static void Main(string[] args){string dynamicJsonString = @"{""metadata"": {""timestamp"": ""2023-10-27T10:00:00Z"",""version"": 1.5},""results"": [{ ""id"": 10, ""status"": ""success"" },{ ""id"": 20, ""status"": ""failed"", ""errorDetail"": ""Timeout"" }],""settings"": null}";try{// 解析為 JObject (如果 JSON 根是對象)JObject jsonObject = JObject.Parse(dynamicJsonString);// 動態訪問屬性(使用索引器)// 可以直接強制類型轉換,但如果屬性不存在或類型不匹配會拋異常string timestamp = (string)jsonObject["metadata"]["timestamp"];double version = (double)jsonObject["metadata"]["version"];Console.WriteLine($"Timestamp: {timestamp}");Console.WriteLine($"Version: {version}");// 訪問嵌套數組JArray resultsArray = (JArray)jsonObject["results"];if (resultsArray != null) // 始終檢查 JArray 是否為 null{Console.WriteLine("Results:");foreach (JToken resultToken in resultsArray) // 遍歷數組中的每個元素 (JObject){int id = resultToken.Value<int>("id"); // 使用 Value<T>(key) 安全獲取子屬性值string status = resultToken.Value<string>("status");string errorDetail = resultToken.Value<string>("errorDetail"); // 如果屬性不存在,Value<string> 會返回 nullConsole.WriteLine($" Id: {id}, Status: {status}, Error Detail: {errorDetail ?? "N/A"}");}}// 訪問可能為 null 的屬性JToken settingsToken = jsonObject["settings"];Console.WriteLine($"Settings token type: {settingsToken.Type}"); // 輸出: NullJToken missingToken = jsonObject["nonexistentKey"];Console.WriteLine($"Missing token: {missingToken}"); // 輸出空行 (C# null)// 修改 JSON 結構 (JObject/JArray 支持修改)jsonObject["newStatus"] = "Processed";jsonObject["results"][0]["status"] = "completed"; // 修改第一個結果的狀態Console.WriteLine("\nModified JSON:");// 可以將 JObject/JArray 轉換為格式化后的 JSON 字符串Console.WriteLine(jsonObject.ToString(Formatting.Indented));}catch (JsonReaderException ex){Console.WriteLine($"JSON Parsing Error: {ex.Message}");}catch (Exception ex){// 捕獲強制類型轉換失敗、訪問 null token 的屬性等異常Console.WriteLine($"發生錯誤: {ex.Message}");}}
}
使用 JToken
/JObject
/JArray
進行動態解析非常靈活,但犧牲了編譯時類型安全,需要更多的運行時檢查來確保數據存在且類型正確。
3. JToken 的“判空”:理解 C# null
與 JSON null
在使用 JToken
系列進行動態訪問時,正確判斷一個 JToken
是否“空”或“不存在”非常重要,這也是初學者常見的困惑點。
- C#
null
引用: 當你使用索引器(如jsonObject["missing_key"]
)嘗試訪問一個在 JSON 中 不存在 的屬性時,Newtonsoft.Json
會返回 C# 的null
引用。 - JSON
null
值: JSON 本身支持null
值 ("key": null
)。當解析到這樣的值時,你會得到一個有效的JToken
對象,它的Type
屬性是JTokenType.Null
。
如何判斷:
using Newtonsoft.Json.Linq;
using System;string json = @"{ ""name"": ""Test"", ""nullableAge"": null, ""emptyArray"": [], ""emptyObject"": {} }";
JObject obj = JObject.Parse(json);JToken existingToken = obj["name"]; // 代表一個字符串值
JToken nullableToken = obj["nullableAge"]; // 代表 JSON null 值
JToken missingToken = obj["nonexistentKey"]; // 是 C# null 引用
JToken emptyArrayToken = obj["emptyArray"]; // 代表一個空數組
JToken emptyObjectToken = obj["emptyObject"]; // 代表一個空對象// 1. 檢查是否是 C# 的 null 引用 (屬性不存在)
if (missingToken == null)
{Console.WriteLine("missingToken 是 C# null"); // 輸出
}// 2. 檢查是否代表 JSON null 值 ("key": null)
if (nullableToken != null && nullableToken.Type == JTokenType.Null)
{Console.WriteLine("nullableToken 代表 JSON null 值"); // 輸出
}// 3. 最常見的組合檢查:屬性不存在 或 屬性值為 JSON null
bool IsNullOrMissing(JToken token)
{return token == null || token.Type == JTokenType.Null;
}if (IsNullOrMissing(missingToken)) Console.WriteLine("missingToken is null or JSON null"); // 輸出
if (IsNullOrMissing(nullableToken)) Console.WriteLine("nullableToken is null or JSON null"); // 輸出
if (!IsNullOrMissing(existingToken)) Console.WriteLine("existingToken 不是 null 或 JSON null"); // 輸出// 4. 檢查是否是空數組或空對象
if (emptyArrayToken is JArray arr && arr.Count == 0)
{Console.WriteLine("emptyArrayToken 是一個空數組"); // 輸出
}if (emptyObjectToken is JObject objToken && objToken.Count == 0)
{Console.WriteLine("emptyObjectToken 是一個空對象"); // 輸出
}
理解這幾者之間的區別,是高效使用 JToken
的關鍵。
第二部分:發送 application/json
POST 請求
發送 POST 請求并附帶 JSON 數據是與 Web API 交互的基本操作。在 .NET 中,我們通常使用 HttpClient
類來完成 HTTP 請求。結合 Newtonsoft.Json
,我們可以輕松地將 C# 對象序列化為 JSON 并作為請求體發送。
以下是具體步驟:
- 創建要發送數據的 C# 對象:
定義一個類來表示你想要發送的數據結構。
using System.Collections.Generic;public class OrderRequest{public string ProductId { get; set; }public int Quantity { get; set; }public string CustomerName { get; set; }public decimal TotalAmount { get; set; }public List<string> Options { get; set; }}
- 使用
HttpClient
發送 POST 請求:
using System;using System.Net.Http;using System.Text; // For Encodingusing System.Threading.Tasks; // For async/awaitusing System.Collections.Generic; // For List<string>using Newtonsoft.Json; // For JsonConvertpublic class HttpClientPostExample{// 最佳實踐: 重用 HttpClient 實例private static readonly HttpClient _httpClient = new HttpClient();public static async Task PostJsonData(string url, OrderRequest orderDetails){try{// 1. 將 C# 對象序列化為 JSON 字符串string jsonPayload = JsonConvert.SerializeObject(orderDetails);Console.WriteLine($"Sending JSON payload: {jsonPayload}");// 2. 創建 StringContent,指定內容、編碼和 Content-Type 為 application/jsonvar content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");// 3. 使用 HttpClient 發送 POST 請求HttpResponseMessage response = await _httpClient.PostAsync(url, content);// 4. 檢查響應狀態碼// response.EnsureSuccessStatusCode(); // 如果狀態碼不是 2xx 會拋異常Console.WriteLine($"Response Status Code: {response.StatusCode}");// 5. 讀取響應內容 (如果需要)string responseBody = await response.Content.ReadAsStringAsync();Console.WriteLine($"Response Body: {responseBody}");// 6. 如果響應也是 JSON,可以反序列化// try// {// // 假設響應是一個表示處理結果的 JSON 對象// var responseResult = JsonConvert.DeserializeObject<OrderResponse>(responseBody);// Console.WriteLine($"Order Status: {responseResult.Status}");// }// catch (JsonReaderException jsonEx)// {// Console.WriteLine($"Failed to parse response body as JSON: {jsonEx.Message}");// }}catch (HttpRequestException e){Console.WriteLine($"HTTP Request Error: {e.Message}");// 在 .NET 5+ 可以通過 e.StatusCode 獲取具體狀態碼}catch (Exception ex){Console.WriteLine($"An unexpected error occurred: {ex.Message}");}}// 示例響應類 (如果你的 API 返回 JSON)public class OrderResponse{public string Status { get; set; }public string OrderId { get; set; }}public static async Task Main(string[] args){// 替換成你的實際 POST 請求 URLstring apiUrl = "YOUR_API_ENDPOINT_URL_HERE"; // 例如: "https://httpbin.org/post"// 創建要發送的數據對象var myOrder = new OrderRequest{ProductId = "ABC-123",Quantity = 2,CustomerName = "John Doe",TotalAmount = 150.75m,Options = new List<string> { "Gift Wrap", "Express Shipping" }};Console.WriteLine($"Sending order POST request to {apiUrl}...");// 調用發送方法await PostJsonData(apiUrl, myOrder);Console.WriteLine("Request process finished.");}}// 定義之前創建的 OrderRequest 類public class OrderRequest{public string ProductId { get; set; }public int Quantity { get; set; }public string CustomerName { get; set; }public decimal TotalAmount { get; set; }public List<string> Options { get; set; }}
}
關鍵點解釋:
HttpClient
生命周期: 在示例中使用了靜態的_httpClient
實例。這是推薦的做法,避免了創建和銷毀過多HttpClient
實例可能導致的 Socket Exhaustion 問題。JsonConvert.SerializeObject(orderDetails)
: 將OrderRequest
對象轉換為 JSON 字符串。Newtonsoft.Json
會處理屬性名、值類型等。new StringContent(...)
: 創建一個HttpContent
對象,它承載了要發送的數據。我們傳入 JSON 字符串,指定編碼為Encoding.UTF8
,并設置Content-Type
為"application/json"
。這個 Content-Type 頭部是服務器用來識別請求體數據格式的關鍵。await _httpClient.PostAsync(url, content)
: 發送異步 POST 請求。HttpClient
會將content
作為請求體發送到指定的 URL。- 異步操作 (
async
/await
): HTTP 請求是 I/O 密集型操作,使用異步方式 (async
/await
) 可以避免阻塞調用線程,提高應用程序的響應性和可伸縮性,特別是在處理多個并發請求時。
總結
Newtonsoft.Json
是一個強大而靈活的庫,為 C# 開發者提供了完整的 JSON 處理能力。無論是將復雜的 JSON 數據映射到強類型 C# 對象進行安全可靠的訪問,還是使用 JToken
系列進行動態探索和操作未知結構的 JSON,它都能勝任。結合 HttpClient
發送 application/json
類型的 POST 請求,是現代 C# 應用與 Web API 交互的基石。
雖然 .NET Core 3.0 以后引入了內置的 System.Text.Json
,它在某些場景下提供更好的性能,并且是微軟官方在新的 .NET 版本中推薦的內置 JSON 庫。然而,Newtonsoft.Json
憑借其豐富的功能集、成熟穩定性和龐大的現有代碼庫,在許多項目中仍然是不可或缺的選擇。
希望本文能幫助你更好地理解和應用 Newtonsoft.Json
在 C# 中的 JSON 解析和 POST 請求場景!