1. 介绍
Newtonsoft.Json 可以说是个“老牌选手”,这是褒义的。多年来它几乎是 .NET 生态里处理 JSON 的 事实标准,早在 System.Text.Json 出现之前就被大量使用。数以百万计的项目、成千上万的库和框架(直到最近,很多 ASP.NET Core 的部分也曾基于它)都在用 Json.NET。
它的优点包括:
- 功能丰富且灵活:Json.NET 提供了大量设置、属性和自定义点,能高度定制序列化与反序列化流程。它能做一些 System.Text.Json 做起来比较吃力甚至做不了的事。
- 对“不完美”的 JSON 更宽容:来自外部系统的数据往往不完美 — Json.NET 通常对这类情况更耐受,能更轻松地反序列化。
- 向后兼容:如果项目或库依赖于 Json.NET,掌握它仍然很有必要。
当然,System.Text.Json 在性能上更好,且更符合现代需求。但当你需要特殊逻辑或极致灵活性时,Newtonsoft.Json 依然很强。
如何安装 Newtonsoft.Json?
因为这是第三方库,通过 NuGet 添加包:
- 打开项目。
- 在 Solution Explorer 中右键点击项目。
- 选择 "Manage NuGet Packages..."。
- 在搜索框里输入 Newtonsoft.Json。
- 选中包并点击 "Install"。
安装完成后会出现新的依赖 —— 现在就可以用 Json.NET 了!
Newtonsoft.Json 和 System.Text.Json 的功能对比
| 功能 | System.Text.Json | Newtonsoft.Json |
|---|---|---|
| 简单的序列化/反序列化 | 是 | 是 |
| 对属性的 attribute 支持 | 部分 | 完整 |
| 自定义转换器 | 是 | 是 |
| 对私有字段的处理 | 否 | 是 |
| 处理动态结构 | 有限制 | 是 |
| 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,行为由参数和设置控制。
示例:不同的格式化方式
// 美观可读的 JSON
string prettyJson = JsonConvert.SerializeObject(player1, Formatting.Indented);
// 紧凑的(压缩)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 支持一套强大的属性系统,可以直接在类上调整序列化行为。
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; }
}
(关于 converter 的细节在下面会讲。)
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. 复杂结构:循环引用,自引用对象
有时对象之间互相引用。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 结构未知或会动态变化时,可以用动态对象而不强制在 C# 里定义类型。
主要类型:
- 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);
GO TO FULL VERSION