You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义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:修复自定义转换器的逻辑

如果需要保留自定义转换器,做如下修改:

  1. 移除Module、Module1、Module2类上的[JsonConverter]特性,改为在序列化设置中注册转换器
  2. 修正WriteJson逻辑,避免递归调用
  3. 统一读写的类型字段名
  4. 调整实例创建逻辑,适配带参构造函数
  5. 修正文件路径错误

修正后的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 18:48:02