自定义JsonConverter处理多态序列化时JObject.FromObject抛空引用异常
问题原因&修复方案
核心错误点
- 递归调用引发序列化异常:你给
Module基类及所有子类都标注了[JsonConverter(typeof(CustomJsonConverter))]特性,在WriteJson方法中调用JObject.FromObject(value, serializer)时,序列化器会再次匹配到该自定义转换器,触发无限递归,最终导致运行异常。 - 类型字段名不匹配:
WriteJson中新增的类型标识字段名为type,但ReadJson中读取的是$type,字段名不一致导致反序列化时无法获取类型信息,抛出异常。 - 文件路径错误:
LoadJson方法中打开文件的路径误写为"$mapping.json",多了$符号,和保存时的文件名"mapping.json"不匹配,读取不到文件后map变量为null,赋值Modules时触发空引用。 - 无无参构造导致实例创建失败:
Module基类和子类都只定义了带参数的构造函数,contract.DefaultCreator()默认调用无参构造创建实例,找不到对应构造函数时会返回null,触发空引用。
修复方案
方案1:直接使用内置多态序列化(最简便,无需自定义转换器)
Newtonsoft.Json的TypeNameHandling.All本身就支持多态类型的序列化/反序列化,完全满足你的需求,直接移除所有Module相关类上的[JsonConverter]特性即可正常运行,不需要额外编写自定义转换器。
额外需要修正的代码只有LoadJson中的文件名:
// 错误写法 using (StreamReader file = File.OpenText("$mapping.json")) // 修正为 using (StreamReader file = File.OpenText("mapping.json"))
方案2:修复自定义转换器的逻辑
如果需要保留自定义转换器,做如下修改:
- 移除
Module、Module1、Module2类上的[JsonConverter]特性,改为在序列化设置中注册转换器 - 修正
WriteJson逻辑,避免递归调用 - 统一读写的类型字段名
- 调整实例创建逻辑,适配带参构造函数
- 修正文件路径错误
修正后的CustomJsonConverter示例:
public class CustomJsonConverter : JsonConverter { // 限定只处理Module及其子类,避免匹配无关类型 private static readonly List<Type> _targetTypes = Assembly.GetAssembly(typeof(Module)) .GetTypes() .Where(t => typeof(Module).IsAssignableFrom(t)) .ToList(); public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { // 创建新的序列化器,排除当前转换器避免递归 var newSerializer = new JsonSerializer(); foreach (var converter in serializer.Converters.Where(c => c != this)) { newSerializer.Converters.Add(converter); } newSerializer.TypeNameHandling = TypeNameHandling.None; newSerializer.ReferenceLoopHandling = serializer.ReferenceLoopHandling; newSerializer.NullValueHandling = serializer.NullValueHandling; var jObj = JObject.FromObject(value, newSerializer); jObj.AddFirst(new JProperty("type", value.GetType().Name)); jObj.WriteTo(writer); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) { return null; } var obj = JObject.Load(reader); // 读取和写入对应的type字段 var typeKey = obj["type"]?.Value<string>(); if (typeKey == null) { throw new InvalidOperationException("Cannot deserialize object w/o 'type' property."); } obj.Remove("type"); var type = _targetTypes.First(x => x.Name == typeKey); // 适配带参构造函数创建实例 var value = Activator.CreateInstance(type, (string)obj["Id"]); using (var subReader = obj.CreateReader()) { serializer.Populate(subReader, value); } return value; } public override bool CanConvert(Type objectType) { return _targetTypes.Contains(objectType); } }
然后在序列化/反序列化设置中注册转换器:
new JsonSerializerSettings { Converters = new List<JsonConverter> { new CustomJsonConverter() }, // 自定义转换器已经处理了类型,这里可以关闭TypeNameHandling TypeNameHandling = TypeNameHandling.None, ReferenceLoopHandling = ReferenceLoopHandling.Ignore, NullValueHandling = NullValueHandling.Ignore }
内容的提问来源于stack exchange,提问作者b.holz
相关产品推荐
相关产品推荐

