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

Newtonsoft Json序列化嵌套C# Base子类递归添加额外字段失效问题

问题描述

我定义了以下C#类:

public class Base
{
}

public class A : Base
{
    public Base AField { get; set; }
}

public class B : Base
{
    public int I { get; set; }
    public int I2 { get; set; }
}

希望使用Newtonsoft Json序列化所有Base对象时,为每个对象添加Extra1、Extra2等额外字段,并将子类的常规属性放在嵌套的Data对象中。例如序列化A的实例时,预期得到如下JSON:

{
  "Extra1": 1,
  "Extra2": 2,
  "Data": {
    "AField": {
      "Extra1": 1,
      "Extra2": 2,
      "Data": {
        "I": 12345,
        "I2": 6789
      }
    }
  }
}

目前编写了如下JsonConverter:

public class BaseConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
    {
        if (value == null)
        {
            writer.WriteNull();
            return;
        }

        var token = JToken.FromObject(value);
        var obj = new JObject
        {
            ["Extra1"] = 1,
            ["Extra2"] = 2, // etc
            ["Data"] = token
        };
        obj.WriteTo(writer);
    }

    public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType.IsSubclassOf(typeof(Base)) || objectType == typeof(Base);
    }
}

该转换器序列化B的实例时正常工作,但递归序列化失效:序列化A的实例时,实际得到的JSON如下:

{
  "Extra1": 1,
  "Extra2": 2,
  "Data": {
    "AField": {
      "I": 12345,
      "I2": 6789
    }
  }
}

即AField未被处理成带额外字段和Data嵌套的结构,但调试时WriteJson方法确实被调用了AField对象。请问问题出在哪里?

问题原因与修复方案

问题根源

问题出在JToken.FromObject(value)这一行。调用这个方法时,它会默认创建一个新的JsonSerializer实例,而非使用传入的serializer参数。这个新序列化器不包含你注册的BaseConverter,因此序列化AField时,虽然WriteJson被调用,但生成的token是未经过转换器处理的原始对象结构,之后你将这个原始结构放到Data字段里,导致最终输出中AField没有嵌套Extra字段和Data结构。

另外,调试时看到WriteJson被调用是因为外层转换器触发了,但这个调用的结果并未被正确保留——你用JToken.FromObject重新序列化了一遍对象,覆盖了之前的转换结果。

修复代码

修改WriteJson方法,使用传入的serializer生成JToken,同时避免循环调用转换器导致栈溢出:

public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
    if (value == null)
    {
        writer.WriteNull();
        return;
    }

    // 创建临时序列化器,移除当前转换器防止循环调用
    var tempSerializer = new JsonSerializer();
    foreach (var converter in serializer.Converters.Where(c => c.GetType() != typeof(BaseConverter)))
    {
        tempSerializer.Converters.Add(converter);
    }
    // 复制原序列化器的其他配置
    tempSerializer.ContractResolver = serializer.ContractResolver;
    tempSerializer.NullValueHandling = serializer.NullValueHandling;
    // 可根据需求复制更多配置,如Formatting、DefaultValueHandling等

    // 使用临时序列化器生成未经过当前转换器处理的原始JToken
    var token = JToken.FromObject(value, tempSerializer);

    var obj = new JObject
    {
        ["Extra1"] = 1,
        ["Extra2"] = 2,
        ["Data"] = token
    };
    obj.WriteTo(writer);
}

修复逻辑说明

  1. 复用原序列化器的配置,但移除当前BaseConverter,避免调用JToken.FromObject时再次触发本转换器,造成无限递归。
  2. 生成的token是对象的原始属性结构,之后将其放入Data字段并添加额外的Extra字段,这样递归序列化时,每个Base子类对象都会被正确包裹。

简化写法(适用场景有限)

如果不需要保留原序列化器的其他自定义转换器和配置,也可以直接创建不带任何转换器的序列化器:

var token = JToken.FromObject(value, new JsonSerializer());

但这种方式会丢失原序列化器的契约解析器、空值处理等配置,仅推荐在简单场景下使用。

内容的提问来源于stack exchange,提问作者xnsc

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 05:57:01