为何JsonConvert.DeserializeObject非泛型方法不使用指定JsonConverter?
你遇到的这个问题是Newtonsoft.Json在处理带$type的非泛型反序列化时的一个典型行为差异:泛型DeserializeObject<T>会优先检查全局转换器是否匹配目标类型T,而非泛版则会先通过$type解析出类型,再默认使用该类型的构造函数创建实例,不会主动触发全局转换器(除非类型契约明确指定)。
从你的示例输出也能看出来:泛型调用时走了ExampleConverter.ReadJson,而非泛型调用直接触发了Example的默认构造函数,完全跳过了转换器。
解决方案
下面提供三种可行的解决思路,你可以根据自己的场景选择:
1. 给目标类型标记[JsonConverter]特性(最简单直接)
如果可以修改目标类的代码,直接在类上标记对应的转换器特性,这样不管是泛型还是非泛型反序列化,都会强制使用你的转换器:
[JsonConverter(typeof(ExampleConverter))] public class Example { public Example(Guid guid) { Console.WriteLine("...Example Ctor: " + guid.ToString()); } public string ExampleProp { get; set; } }
修改后再运行你的测试,非泛型反序列化就会触发ExampleConverter.ReadJson,不再调用默认构造函数。
2. 自定义契约解析器(不修改目标类的情况)
如果不能修改目标类的代码,可以通过自定义IContractResolver,为指定类型绑定转换器,让Json.NET在解析$type后仍然使用你的转换器:
public class CustomContractResolver : DefaultContractResolver { private readonly JsonConverter _targetConverter; private readonly Type _targetType; public CustomContractResolver(Type targetType, JsonConverter converter) { _targetType = targetType; _targetConverter = converter; } protected override JsonContract CreateContract(Type objectType) { var contract = base.CreateContract(objectType); // 为目标类型绑定转换器 if (objectType == _targetType) { contract.Converter = _targetConverter; } return contract; } }
然后在反序列化配置中使用这个契约解析器:
var readSettings = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All, ContractResolver = new CustomContractResolver(typeof(Example), new ExampleConverter()) };
这样非泛型反序列化时,Json.NET解析出Example类型后,会自动使用你指定的转换器。
3. 手动解析$type后调用泛型方法(灵活适配多类型场景)
如果需要处理多种类型,不想逐个绑定转换器,可以先手动读取JSON中的$type字段,获取目标类型后通过反射调用泛版DeserializeObject:
// 先解析$type获取目标类型 var jObject = JObject.Parse(json); var typeFullName = (string)jObject["$type"]; var targetType = Type.GetType(typeFullName); // 反射调用泛型反序列化方法 var deserializeMethod = typeof(JsonConvert) .GetMethod("DeserializeObject", new[] { typeof(string), typeof(JsonSerializerSettings) }) .MakeGenericMethod(targetType); var e3 = deserializeMethod.Invoke(null, new object[] { json, readSettings });
这种方式本质上还是复用了泛型反序列化的逻辑,自然会触发你的全局转换器。
针对IOC容器的适配
你提到需要通过IOC容器解析实例,只需要在转换器的ReadJson方法中替换实例创建逻辑即可,比如:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer){ Console.WriteLine("...ExampleConverter.ReadJson..."); // 从IOC容器获取实例(这里替换成你的容器调用逻辑) var instance = YourIocContainer.Resolve(objectType); // 填充属性 serializer.Populate(reader, instance); return instance; }
这样就完全不需要目标类有默认构造函数,所有实例都通过IOC创建并完成数据填充。
内容的提问来源于stack exchange,提问作者mike

