在C#项目开发中,Dictionary是存放键值对数据最常用的集合类型。当我们需要把数据保存到文件、缓存或者通过网络传输时,就绕不开序列化这个环节。不过Dictionary的结构和普通类不同,它的键和值都可以是任意类型,直接序列化时常会遇到键类型不支持、嵌套结构解析失败等问题。这篇文章就来系统地讲讲C#中序列化和反序列化Dictionary的几种主流做法,并结合实际场景分析各自的适用范围。

使用Newtonsoft.Json序列化Dictionary
Newtonsoft.Json也就是大家常说的Json.NET,是.NET生态中使用最广泛的JSON库。它对Dictionary的支持非常成熟,几乎不需要额外配置就能完成序列化和反序列化。序列化时,字典的键会直接变成JSON对象的属性名,值则对应属性的值。
下面是一个基础示例,展示如何把一个字符串键的字典转成JSON字符串,再还原回来:
using Newtonsoft.Json;
using System.Collections.Generic;
var dict = new Dictionary<string, int>
{
{ "apple", 10 },
{ "banana", 25 },
{ "orange", 8 }
};
// 序列化
string json = JsonConvert.SerializeObject(dict, Formatting.Indented);
// 反序列化
var restored = JsonConvert.DeserializeObject<Dictionary<string, int>>(json);
Console.WriteLine(json);
Console.WriteLine(restored["banana"]); // 输出 25
这段代码执行后,JSON字符串的内容是一个对象形式,形如{"apple":10,"banana":25,"orange":8}。反序列化时只要明确指定泛型类型Dictionary<string, int>,Json.NET会自动完成类型匹配。
需要注意的一点是,如果字典的值是复杂类型,比如自定义类,反序列化同样没有障碍,Json.NET会递归处理嵌套结构。此外,JsonConvert.DeserializeObject在输入为空字符串时返回null,实际项目中最好加一层判空保护,避免后续代码抛出空引用异常。
使用System.Text.Json处理字典序列化
从.NET Core 3.0开始,微软推出了内置的System.Text.Json库,它以高性能、低分配著称,现在已经成为新项目的默认选择。用它处理Dictionary的基础用法和Json.NET类似,但细节上有一些差异,特别是非字符串键的处理上,两者行为并不一致。
先看基础用法:
using System.Text.Json;
using System.Collections.Generic;
var dict = new Dictionary<string, object>
{
{ "name", "张三" },
{ "age", 28 },
{ "scores", new List<int> { 90, 85, 77 } }
};
// 序列化
string json = JsonSerializer.Serialize(dict, new JsonSerializerOptions
{
WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
});
// 反序列化
var restored = JsonSerializer.Deserialize<Dictionary<string, object>>(json);
Console.WriteLine(restored["name"]);
这里有一个中文开发者经常踩的坑:System.Text.Json默认会把非ASCII字符转义成Unicode编码,序列化结果里中文会变成\u5F20\u4E09这样的形式。虽然功能上不影响正确性,但可读性很差,也不利于排查问题。解决办法就是像上面那样设置JavaScriptEncoder.UnsafeRelaxedJsonEscaping,让中文原样输出。
另一个重要差异是非字符串键的支持。System.Text.Json在序列化时支持int、枚举等非字符串键的字典,会自动把键转成字符串。但反序列化回非字符串键的字典时,旧版本会抛异常,从.NET 5开始才加入了对键类型转换的支持。如果你的项目还在使用较老版本,遇到Dictionary<int, string>这类结构反序列化失败,可以考虑升级框架版本,或者改用Newtonsoft.Json。
嵌套字典和复杂结构的处理
实际业务中,字典往往会嵌套使用,比如一个配置系统可能用Dictionary<string, Dictionary<string, object>>来存储分组配置。这种结构的序列化本身没有难度,麻烦的地方在于反序列化时object类型的值会变成什么。
以Json.NET为例,反序列化到object时,JSON对象会变成JObject,数组变成JArray,这在后续取值时需要做类型判断。示例代码如下:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
var config = new Dictionary<string, object>
{
{ "database", new Dictionary<string, object>
{
{ "host", "127.0.0.1" },
{ "port", 3306 }
}
}
};
string json = JsonConvert.SerializeObject(config);
var restored = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
var db = restored["database"] as JObject;
string host = db["host"].ToString();
int port = db["port"].ToObject<int>();
Console.WriteLine($"{host}:{port}");
如果用的是System.Text.Json,情况稍有不同:反序列化到object后,JSON对象会变成JsonElement,需要通过GetProperty方法和GetInt32、GetString这类强类型方法来取值。两种方式各有优劣,JObject的索引器写起来更顺手,JsonElement则在解析大JSON时内存占用更小。
对于结构相对固定的数据,更推荐的做法是定义具体的强类型模型类来接收反序列化结果,而不是依赖字典加动态解析。强类型能在编译期发现拼写错误,配合IDE的智能提示,代码的可维护性明显更好。字典更适合键名不确定的场景,比如从外部接口接收动态数据。
二进制序列化及其他方案对比
除了JSON,C#还支持二进制序列化。老的BinaryFormatter曾经是常见选择,但微软已经在官方文档中明确将其标记为不安全,指出它存在反序列化漏洞风险,攻击者可以构造恶意数据在反序列化时执行任意代码。因此在新的.NET版本中,这个类已经被标记为过时,编译时会直接报错,不建议在任何新项目中使用。
替代方案有几种。如果追求跨平台和可读性,JSON仍然是最稳妥的选择;如果追求体积和速度,可以考虑protobuf-net这类第三方二进制库,它对Dictionary有良好的支持,性能和压缩率都远超JSON:
using ProtoBuf;
using System.Collections.Generic;
using System.IO;
[ProtoContract]
public class CacheData
{
[ProtoMember(1)]
public Dictionary<string, string> Items { get; set; }
}
var data = new CacheData
{
Items = new Dictionary<string, string>
{
{ "key1", "value1" },
{ "key2", "value2" }
}
};
using var stream = new MemoryStream();
Serializer.Serialize(stream, data);
stream.Position = 0;
var result = Serializer.Deserialize<CacheData>(stream);
Console.WriteLine(result.Items["key1"]);
在库的选型上,可以从几个维度来决策。性能方面,System.Text.Json在大多数基准测试中比Newtonsoft.Json快一到三倍,高并发场景下优势明显;功能方面,Json.NET更加灵活,对各种特殊类型的兼容性更好,老项目迁移成本低。如果项目基于.NET Core 3.0以上且没有特殊需求,优先使用System.Text.Json;如果历史代码大量依赖JObject、JToken这些API,继续使用Json.NET也没问题,两者完全可以共存。
总结一下,Dictionary的序列化在C#中并不复杂,核心在于根据字典的键值类型选择合适的库,并处理好中文字符编码、非字符串键、嵌套结构这三个常见的坑。掌握这些细节后,无论是做配置存储、缓存持久化还是接口数据传输,都能得心应手。
C#序列化Dictionary反序列化JSON序列化修改时间:2026-09-11 03:38:34