使用Json.Net的TypeNameHandling.Auto时,列表与单个对象序列化类型信息差异问题
为什么Json.Net的TypeNameHandling.Auto在序列化列表时显示类型信息,单个对象却不显示?
嘿,这个问题我之前踩过坑,其实是TypeNameHandling.Auto的工作逻辑在搞鬼!让我给你拆解清楚来龙去脉。
核心原因:TypeNameHandling.Auto的触发规则
TypeNameHandling.Auto的核心逻辑很简单:只有当被序列化对象的实际类型和它的「声明类型」不一致时,才会自动添加$type类型信息。这里的「声明类型」会根据序列化上下文变化:
- 单个根对象:声明类型就是你传给
JsonConvert.SerializeObject的参数类型。如果参数类型和实例类型一致(比如直接传子类实例,或者传基类实例),就不会加类型信息;如果参数是基类类型,但实际是子类实例,才会触发$type添加。 - 集合/列表中的元素:声明类型是集合的泛型参数类型(比如
List<BaseClass>的元素声明类型是BaseClass)。只要集合里的元素实际是子类,就会触发TypeNameHandling.Auto——因为元素的声明类型和实际类型不一致,所以会自动加$type。
你遇到的场景,大概率是序列化单个对象时直接传了子类实例(声明类型=实际类型),而序列化列表时用的是基类泛型列表,里面放了子类元素,所以出现了差异。
复现你的场景的完整代码
我们用代码来直观验证这个逻辑:
using System; using System.Collections.Generic; using Newtonsoft.Json; namespace SerializationTest { public class BaseClass { public int Id { get; set; } } public class DerivedClass : BaseClass { public string Name { get; set; } } class Program { static void Main(string[] args) { // 情况1:直接序列化子类实例(声明类型=实际类型) var directSingle = new DerivedClass { Id = 1, Name = "Direct" }; // 情况2:子类实例赋值给基类变量后序列化(声明类型≠实际类型) BaseClass typedSingle = new DerivedClass { Id = 2, Name = "Typed" }; // 情况3:BaseClass类型列表,元素是子类实例 List<BaseClass> itemList = new List<BaseClass> { new DerivedClass { Id = 3, Name = "List Item" } }; var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto, Formatting = Formatting.Indented }; Console.WriteLine("1. 直接序列化子类实例:"); Console.WriteLine(JsonConvert.SerializeObject(directSingle, settings)); Console.WriteLine("\n2. 序列化基类变量(实际是子类):"); Console.WriteLine(JsonConvert.SerializeObject(typedSingle, settings)); Console.WriteLine("\n3. 序列化BaseClass列表(含子类元素):"); Console.WriteLine(JsonConvert.SerializeObject(itemList, settings)); } } }
输出结果(完全匹配你的现象):
直接序列化子类实例:
{
"Id": 1,
"Name": "Direct"
}序列化基类变量(实际是子类):
{
"$type": "SerializationTest.DerivedClass, SerializationTest",
"Id": 2,
"Name": "Typed"
}序列化BaseClass列表(含子类元素):
[
{
"$type": "SerializationTest.DerivedClass, SerializationTest",
"Id": 3,
"Name": "List Item"
}
]
解决方案
如果你的需求是让单个子类对象也显示类型信息,可以按以下两种方式处理:
- 规范序列化前的类型声明:把子类实例赋值给基类类型的变量后再序列化,像上面的情况2那样,让声明类型和实际类型不一致,触发
TypeNameHandling.Auto添加类型信息。 - 强制添加类型信息(更严格的控制):如果不管声明类型是什么,都要给所有对象加类型信息,可以把
TypeNameHandling设置为TypeNameHandling.Objects或TypeNameHandling.All。但要注意:这样会增加JSON体积,而且反序列化时一定要加自定义绑定器,避免类型注入攻击。
示例代码(带安全绑定器):
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects, // 对所有对象添加类型信息 Formatting = Formatting.Indented, // 安全绑定器:只允许我们自己的基类和子类 SerializationBinder = new CustomSerializationBinder() }; // 自定义序列化绑定器实现 public class CustomSerializationBinder : ISerializationBinder { public Type BindToType(string assemblyName, string typeName) { var allowedTypes = new[] { typeof(BaseClass), typeof(DerivedClass) }; return allowedTypes.FirstOrDefault(t => t.FullName == typeName); } public void BindToName(Type serializedType, out string assemblyName, out string typeName) { assemblyName = serializedType.Assembly.GetName().Name; typeName = serializedType.FullName; } }
内容的提问来源于stack exchange,提问作者codigube
相关产品推荐
相关产品推荐

