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 包管理器控制台运行以下命令:
bash
Install-Package Newtonsoft.Json
第一部分:JSON 解析的艺术 - 将 JSON 转化为 C# 对象
处理 JSON 字符串最常见也最安全的方式是将其映射到预先定义的 C# 类或结构体。Newtonsoft.Json
提供了强大的反序列化功能,能够将 JSON 结构自动转化为对应的 C# 对象。
1. 将 JSON 反序列化为强类型对象 (JsonConvert.DeserializeObject<T>
)
这种方法适用于你知道 JSON 的具体结构,并可以为其定义一个匹配的 C# 类。这是推荐的方式,因为它提供了编译时类型检查,减少了运行时错误。
假设我们有以下 JSON 数据,代表一个用户的信息:
json
{
"userName": "Alice",
"age": 30,
"isActive": true,
"roles": ["Viewer", "Contributor"],
"profile": {
"email": "[email protected]",
"bio": "Software Engineer"
}
}
为了解析它,我们需要定义对应的 C# 类:
csharp
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>
方法进行反序列化:
csharp
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"": ""[email protected]"",
""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[]
:
csharp
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")]
属性来指定映射关系:
csharp
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 字符串到这些动态类型:
csharp
using System;
using Newtonsoft.Json.Linq; // 引入 Newtonsoft.Json.Linq
public 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> 会返回 null
Console.WriteLine($" Id: {id}, Status: {status}, Error Detail: {errorDetail ?? "N/A"}");
}
}
// 访问可能为 null 的属性
JToken settingsToken = jsonObject["settings"];
Console.WriteLine($"Settings token type: {settingsToken.Type}"); // 输出: Null
JToken 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
。
如何判断:
csharp
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# 对象:
定义一个类来表示你想要发送的数据结构。
csharp
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 请求:
csharp
using System;
using System.Net.Http;
using System.Text; // For Encoding
using System.Threading.Tasks; // For async/await
using System.Collections.Generic; // For List<string>
using Newtonsoft.Json; // For JsonConvert
public 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/json
var 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 请求 URL
string 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 请求场景!