强类型反序列化适用于结构固定的json,通过定义c#类直接映射,提升类型安全和代码可读性;2. 动态解析使用jobject/jarray,适合结构不确定或只需访问部分字段的场景,灵活性高;3. 嵌套结构在强类型中通过类嵌套自动映射,在动态解析中通过链式索引访问;4. 数组在强类型中映射为list

C#中使用Json.NET库解析JSON数据,核心在于将JSON字符串映射到C#对象。你可以选择强类型反序列化到预定义的类,或者使用JObject/JArray进行动态解析,这取决于你对JSON结构了解的程度和需求。
解决方案
谈到Json.NET解析JSON,我个人觉得它最迷人的地方在于其兼顾了便捷性和灵活性。很多时候,我们面对的JSON结构并非一成不变,或者我们只想抽取其中某个小片段,这时Json.NET的两种主要思路就派上用场了。
1. 强类型反序列化:当你对JSON结构了如指掌时
这是最推荐也最简洁的方式。如果你的JSON结构是固定的,或者你可以预先定义好对应的C#类,那么直接将JSON字符串反序列化到这些类实例上,能带来极佳的代码可读性和类型安全性。
比如,我们有这样一个JSON:
{
"productId": 101,
"productName": "Laptop Pro",
"price": 1200.50,
"tags": ["electronics", "computers", "portable"],
"details": {
"weightKg": 1.8,
"color": "Space Gray"
}
}你可以这样定义C#类:
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public decimal Price { get; set; }
public List Tags { get; set; }
public ProductDetails Details { get; set; }
}
public class ProductDetails
{
public double WeightKg { get; set; }
public string Color { get; set; }
} 然后,解析就变得异常简单:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
// 假设jsonString是上述JSON内容
string jsonString = @"{
""productId"": 101,
""productName"": ""Laptop Pro"",
""price"": 1200.50,
""tags"": [""electronics"", ""computers"", ""portable""],
""details"": {
""weightKg"": 1.8,
""color"": ""Space Gray""
}
}";
try
{
Product product = JsonConvert.DeserializeObject(jsonString);
Console.WriteLine($"产品ID: {product.ProductId}");
Console.WriteLine($"产品名称: {product.ProductName}");
Console.WriteLine($"价格: {product.Price}");
Console.WriteLine($"第一个标签: {product.Tags?[0]}"); // 注意null条件运算符
Console.WriteLine($"颜色: {product.Details?.Color}");
}
catch (JsonSerializationException ex)
{
Console.WriteLine($"反序列化错误: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"发生未知错误: {ex.Message}");
} 2. 动态解析:当你对JSON结构不确定或只想快速访问部分数据时
有时候,我们可能不清楚JSON的具体结构,或者只想获取其中一两个字段的值,为它专门定义一个类显得有些小题大做。这时,JObject和JArray就显得非常灵活了。它们允许你像操作字典或数组一样,通过键名或索引来访问JSON中的数据。
继续以上面的JSON为例:
using Newtonsoft.Json.Linq; // JObject, JArray都在这个命名空间下
string jsonString = @"{
""productId"": 101,
""productName"": ""Laptop Pro"",
""price"": 1200.50,
""tags"": [""electronics"", ""computers"", ""portable""],
""details"": {
""weightKg"": 1.8,
""color"": ""Space Gray""
}
}";
try
{
JObject jsonObject = JObject.Parse(jsonString);
// 访问基本类型
int productId = (int)jsonObject["productId"];
string productName = (string)jsonObject["productName"];
decimal price = jsonObject["price"].Value(); // 另一种获取值的方式
Console.WriteLine($"产品ID (动态): {productId}");
Console.WriteLine($"产品名称 (动态): {productName}");
// 访问嵌套对象
JObject details = (JObject)jsonObject["details"];
string color = (string)details["color"];
Console.WriteLine($"颜色 (动态): {color}");
// 访问数组
JArray tags = (JArray)jsonObject["tags"];
foreach (JToken tag in tags)
{
Console.WriteLine($"标签: {tag.ToString()}");
}
// 安全地获取可能不存在的属性
string nonExistentProperty = (string)jsonObject["nonExistent"]?.Value();
Console.WriteLine($"不存在的属性: {nonExistentProperty ?? "未找到"}");
}
catch (JsonReaderException ex)
{
Console.WriteLine($"JSON解析错误: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"发生未知错误: {ex.Message}");
} 这种动态解析方式在处理API响应不一致、或者需要快速原型验证时非常有用。它允许你先拿到数据,再决定如何处理,而不用急着定义一堆类。
如何处理JSON中的嵌套结构和数组?
处理JSON中的嵌套结构和数组,其实是Json.NET的强项,无论是强类型还是动态解析,都有非常直观的方法。我发现很多初学者在这里会有点困惑,觉得嵌套就复杂了,但实际上,只要理解了映射关系,它比你想象的要简单。
对于嵌套对象:
在强类型反序列化中,只需要在你的C#父类中,将子对象定义为另一个C#类的实例即可。Json.NET会自动递归地进行映射。比如,如果你的JSON有一个"user": { "name": "Alice", "age": 30 },那么你的RootObject类中就应该有一个public User User { get; set; }属性,而User类则包含Name和Age属性。
// JSON: { "orderId": "123", "customer": { "name": "Bob", "email": "bob@example.com" } }
public class Order
{
public string OrderId { get; set; }
public Customer Customer { get; set; } // 嵌套对象
}
public class Customer
{
public string Name { get; set; }
public string Email { get; set; }
}
// 解析
Order myOrder = JsonConvert.DeserializeObject(jsonString);
Console.WriteLine($"客户姓名: {myOrder.Customer.Name}"); 在使用JObject动态解析时,嵌套对象就像是多层字典。你可以通过链式索引来访问:
JObject orderJson = JObject.Parse(jsonString);
string customerName = (string)orderJson["customer"]["name"]; // 链式访问
Console.WriteLine($"客户姓名 (动态): {customerName}");对于数组:
数组的处理同样直接。强类型反序列化时,JSON数组通常映射到C#的List或T[]。如果数组包含复杂对象,那么T就是你定义的那个复杂对象类。
// JSON: { "productName": "Keyboard", "features": ["mechanical", "rgb", "wireless"] }
public class ProductWithFeatures
{
public string ProductName { get; set; }
public List Features { get; set; } // 字符串数组
}
// JSON: { "teamName": "Devs", "members": [{ "name": "Alice" }, { "name": "Bob" }] }
public class Team
{
public string TeamName { get; set; }
public List Members { get; set; } // 对象数组
}
public class Member
{
public string Name { get; set; }
}
// 解析
ProductWithFeatures keyboard = JsonConvert.DeserializeObject(jsonString1);
Console.WriteLine($"第一个特性: {keyboard.Features[0]}");
Team devTeam = JsonConvert.DeserializeObject(jsonString2);
Console.WriteLine($"第一个成员: {devTeam.Members[0].Name}"); 动态解析时,JSON数组会被解析成JArray。你可以像遍历C#数组一样遍历它,然后对每个JToken元素进行类型转换或进一步解析:
JObject productFeaturesJson = JObject.Parse(jsonString1);
JArray featuresArray = (JArray)productFeaturesJson["features"];
foreach (JToken feature in featuresArray)
{
Console.WriteLine($"特性: {feature.ToString()}");
}
JObject teamJson = JObject.Parse(jsonString2);
JArray membersArray = (JArray)teamJson["members"];
foreach (JObject member in membersArray) // 注意这里直接转为JObject
{
Console.WriteLine($"成员姓名: {(string)member["name"]}");
}关键在于,Json.NET的设计理念就是将JSON的层级结构自然地映射到C#的对象图,无论是显式定义类还是通过JObject/JArray的嵌套访问,都保持了高度的一致性。
解析时遇到数据类型不匹配或缺失字段怎么办?
在实际开发中,JSON数据源往往不像示例那样完美,可能会出现字段缺失、数据类型不匹配,甚至额外字段的情况。处理这些“不完美”是健壮代码的体现,Json.NET提供了不少策略来应对。
1. 缺失字段:
-
默认值和可空类型: 如果一个字段可能缺失,最简单的方法是将其C#属性定义为可空类型(如
int?,decimal?)或者赋一个默认值。public class Product { public int ProductId { get; set; } public string ProductName { get; set; } public decimal? Price { get; set; } // Price可能缺失,所以用decimal? public string Description { get; set; } = "无描述"; // 缺失时使用默认值 }当JSON中没有
Price字段时,Price属性会被设为null。没有Description时,会保持“无描述”。 -
JsonProperty属性的Required选项: 如果某个字段是强制性的,缺失就应该报错,你可以使用[JsonProperty(Required = Required.Always)]。
PHP Apache和MySQL 网页开发初步下载本书全面介绍PHP脚本语言和MySOL数据库这两种目前最流行的开源软件,主要包括PHP和MySQL基本概念、PHP扩展与应用库、日期和时间功能、PHP数据对象扩展、PHP的mysqli扩展、MySQL 5的存储例程、解发器和视图等。本书帮助读者学习PHP编程语言和MySQL数据库服务器的最佳实践,了解如何创建数据库驱动的动态Web应用程序。
public class User { [JsonProperty(Required = Required.Always)] // 强制要求此字段必须存在 public string Username { get; set; } public int Age { get; set; } } // 如果JSON中没有Username,会抛出JsonSerializationExceptionRequired.Default是默认值,表示如果JSON中没有该字段,则使用C#属性的默认值(null或0等)。Required.AllowNull表示字段可以缺失,如果存在但为null,则也允许。 -
动态解析时的空值检查: 使用
JObject时,在访问属性前进行null检查,或者使用C# 8的空条件运算符?.。JObject data = JObject.Parse("{ \"name\": \"Alice\" }"); string email = (string)data["email"]?.Value(); // 如果email不存在,email会是null Console.WriteLine($"Email: {email ?? "N/A"}");
2. 数据类型不匹配:
这是最常见的错误源之一。比如JSON里一个数字字段,但C#里定义成了字符串。
-
JsonConvert.DeserializeObject抛出异常: 默认情况下,如果类型不匹配,Json.NET会抛出JsonSerializationException。这是好事,因为它提醒你数据结构与模型不符。// JSON: { "age": "thirty" } 但C#类中 age 是 int public class Person { public int Age { get; set; } } // JsonConvert.DeserializeObject(json) 会抛出异常 处理方式通常是在外层用
try-catch捕获这个异常,然后根据业务需求进行错误记录、重试或返回错误信息。 -
自定义
JsonConverter: 对于复杂的类型转换,例如JSON中的日期字符串格式不固定,或者需要将某个特定字符串映射到枚举类型,你可以实现一个自定义的JsonConverter。这提供了非常强大的控制力,让你能精确定义如何从JSON读取数据以及如何写入JSON。// 假设JSON中的日期是 "2023/10/27" 而不是标准ISO 8601 public class CustomDateTimeConverter : JsonConverter
{ public override DateTime ReadJson(JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.String) { return DateTime.ParseExact((string)reader.Value, "yyyy/MM/dd", null); } return default; // 或者抛出异常 } // ... WriteJson 方法 } public class Event { [JsonConverter(typeof(CustomDateTimeConverter))] public DateTime EventDate { get; set; } } 然后
JsonConvert.DeserializeObject就会使用你的自定义转换器。(json) -
JToken.ToObject: 在动态解析时,如果你不确定某个() JToken的实际类型,但想尝试将其转换为特定C#类型,可以使用JToken.ToObject。它会尝试进行转换,如果失败会抛出异常。() JObject data = JObject.Parse("{ \"count\": \"123\" }"); try { int count = data["count"].ToObject(); // 尝试将字符串"123"转为int Console.WriteLine($"Count: {count}"); } catch (Exception ex) { Console.WriteLine($"转换失败: {ex.Message}"); }
总的来说,处理这些异常情况,核心思想是:预判可能的问题,利用Json.NET提供的特性(如可空类型、Required属性、自定义转换器),并在最外层进行try-catch,确保程序的健壮性。
性能优化与高级用法有哪些考量?
Json.NET作为一个广泛使用的库,在性能和高级功能方面都有不少值得探讨的地方。日常使用可能很少触及,但当处理大量数据、追求极致性能,或者有特殊序列化需求时,这些考量就显得很重要了。
1. 性能优化:
避免不必要的中间对象: 如果你只需要JSON中的一小部分数据,并且这些数据是扁平的,直接使用
JObject或JArray的索引访问通常比先反序列化成一个庞大的强类型对象再取值更高效,因为省去了创建大量C#对象的开销。-
流式解析(
JsonTextReader): 对于非常大的JSON文件(比如几百MB甚至GB级别),一次性将整个JSON字符串加载到内存并解析成JObject或强类型对象可能会导致内存溢出。这时,使用JsonTextReader进行流式读取是最佳实践。它允许你逐个读取JSON令牌(如开始对象、属性名、值、结束对象等),从而在不将整个JSON加载到内存的情况下处理数据。这就像读文件一样,一行一行地读,而不是一次性读完。using (JsonTextReader reader = new JsonTextReader(new StringReader(largeJsonString))) { while (reader.Read()) { if (reader.TokenType == JsonToken.PropertyName && reader.Value.ToString() == "targetProperty") { reader.Read(); // 读取属性值 Console.WriteLine($"找到目标属性值: {reader.Value}"); break; } } }当然,流式解析的缺点是需要手动管理状态,代码会复杂一些。
重用
JsonSerializerSettings: 如果你在应用程序中多次进行序列化/反序列化,并且使用了自定义设置(如日期格式、命名策略等),最好将JsonSerializerSettings实例创建一次并重用,而不是每次都新建。这可以减少GC压力和初始化开销。-
选择合适的序列化/反序列化方法:
JsonConvert.SerializeObject和JsonConvert.DeserializeObject是静态方法,内部会创建JsonSerializer实例。如果需要频繁操作且有特定设置,直接创建和重用JsonSerializer实例会更高效。JsonSerializer serializer = new JsonSerializer(); serializer.Converters.Add(new CustomDateTimeConverter()); // ... 其他设置 using (StringReader sr = new StringReader(jsonString)) using (JsonTextReader reader = new JsonTextReader(sr)) { MyObject obj = serializer.Deserialize(reader); }
2. 高级用法:
自定义序列化/反序列化逻辑(
JsonConverter): 前面提到了处理类型不匹配,但JsonConverter的用途远不止于此。你可以用它来处理多态(根据JSON中的某个字段决定反序列化成哪个子类)、特殊数据结构(如将一个对象序列化成数组形式)、或者对特定属性进行加密/解密。这需要继承JsonConverter并实现ReadJson和WriteJson方法。-
条件序列化(
ShouldSerialize): 有时候你可能不希望所有属性都被序列化到JSON中,例如某些属性只有在满足特定条件时才需要。Json.NET允许你在类中定义一个名为ShouldSerialize[PropertyName]的方法,返回bool,来控制对应属性是否被序列化。public class UserProfile { public string Username { get; set; } public string Email { get; set; } public bool IsAdmin { get; set; } // 只有当IsAdmin为true时才序列化Email public bool ShouldSerializeEmail() { return IsAdmin; } } JsonProperty属性的更多用法: 除了Required,JsonProperty还有很多其他有用的特性,比如PropertyName用于C#属性名和JSON字段名不一致的情况,Order用于控制JSON输出中属性的顺序,DefaultValue用于当属性值为默认值时是否序列化等。处理循环引用(
ReferenceLoopHandling): 当你的C#对象图存在循环引用(A引用B,B又引用A)时,默认序列化会进入无限循环并抛出异常。你可以通过ReferenceLoopHandling设置来控制行为,例如Ignore(忽略循环引用)、Serialize(序列化所有引用,可能导致无限循环)、Error(默认,抛出异常)。通常设置为ReferenceLoopHandling.Ignore或ReferenceLoopHandling.Serialize配合PreserveReferencesHandling来解决。错误处理回调(
Error事件): 在序列化或反序列化过程中,你可以通过JsonSerializerSettings.Error事件注册回调,来捕获和处理发生在特定属性上的错误,而不是直接抛出整个操作的异常。这在处理部分有效但部分损坏的JSON时非常有用。
这些高级用法,就像是Json









