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); }
修复逻辑说明
- 复用原序列化器的配置,但移除当前
BaseConverter,避免调用JToken.FromObject时再次触发本转换器,造成无限递归。 - 生成的
token是对象的原始属性结构,之后将其放入Data字段并添加额外的Extra字段,这样递归序列化时,每个Base子类对象都会被正确包裹。
简化写法(适用场景有限)
如果不需要保留原序列化器的其他自定义转换器和配置,也可以直接创建不带任何转换器的序列化器:
var token = JToken.FromObject(value, new JsonSerializer());
但这种方式会丢失原序列化器的契约解析器、空值处理等配置,仅推荐在简单场景下使用。
内容的提问来源于stack exchange,提问作者xnsc

