在序列化派生类型集合时如何正确使用[JsonIgnore]特性
环境:.NET 6 WebAPI应用
我有基类Base和派生类Derived,二者均可序列化输出为JSON发送给客户端,代码如下:
public class Base { public int? Prop1 { get; set; } public string? Prop2 { get; set; } public long? Prop3 { get; set; } // 其他属性 } public class Derived: Base { [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public new int? Prop1 { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public new string? Prop2 { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public new long? Prop3 { get; set; } // 其他属性 }
还有一个包含Base对象集合的模型类:
public class Model { public List<Base>? Properties { get; set; } // 其他属性 }
我希望序列化Properties集合中的Base对象时始终保留所有键,而序列化Derived对象时忽略值为null的键。预期效果如下:
var baseModel = new Model{ Properties = new List<Base>{ new Base { Prop1 = 1 } } }; var serialized = JsonSerializer.Serialize(baseModel); // 预期返回: '{ "properties": { "Prop1": 1, "Prop2": null, "Prop3": null }}' var derivedModel = new Model { Properties = new List<Derived>{ new Derived { Prop1 = 1 }}}; // 因类型不匹配无法编译 var derivedModel2 = new Model { Properties = new List<Base>{ (Base)new Derived { Prop1 = 1 }}}; // 可运行,但实际返回: '{ "properties": { "Prop1": 1, "Prop2": null, "Prop3": null }}' // 我需要的是: '{ "properties": { "Prop1": 1 } }'
更新说明
我曾考虑使用泛型类,但当前模型的使用方式如下(简化版):
public class BusinessLogic: IBusinessLogic { // 含依赖注入的构造函数等 public async Task<Model> GetStuff(...) { var model = GetModelInternal(...); return model; } } public interface IBusinessLogic { Task<Model> GetStuff(...); } public class MyController: ApiController { protected readonly IBusinessLogic _bl; public MyController(..., IBusinessLogic bl) { _bl = bl; } [HttpGet] public async Task<IActionResult> GetStuff(bool baseOrDerived, ...) { var model = await _bl.GetModel(baseOrDerived, ...); return Json(model); } }
返回对象的类型(Base或Derived)需取决于API客户端传入的baseOrDerived参数。若使用泛型,需将类型参数逐层传递至控制器,还需修改IBusinessLogic/BusinessLogic,通过ServiceProvider动态构造泛型实例,而这类需求不止一处,实现成本过高。
1. 自定义JsonConverter处理多态序列化
编写针对Base类型的自定义转换器,序列化时判断对象实际类型,应用对应规则:
public class BaseDerivedConverter : JsonConverter<Base> { private readonly JsonSerializerOptions _baseOptions = new() { DefaultIgnoreCondition = JsonIgnoreCondition.Never }; private readonly JsonSerializerOptions _derivedOptions = new() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault }; public override Base? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { // 反序列化逻辑按需实现,无需可抛出异常或返回默认值 throw new NotImplementedException(); } public override void Write(Utf8JsonWriter writer, Base value, JsonSerializerOptions options) { if (value is Derived derived) { JsonSerializer.Serialize(writer, derived, _derivedOptions); } else { JsonSerializer.Serialize(writer, value, _baseOptions); } } }
在Program.cs中注册转换器:
builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new BaseDerivedConverter()); });
此方式无需修改现有模型和业务逻辑,序列化时自动根据对象实际类型选择规则。
2. 移除Derived类的new关键字,改用自定义ContractResolver
调整Derived类实现,去掉new属性,通过自定义ContractResolver动态设置属性忽略规则:
修改Derived类:
public class Derived : Base { }
编写自定义ContractResolver:
public class BaseDerivedContractResolver : DefaultContractResolver { protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var property = base.CreateProperty(member, memberSerialization); if (member.DeclaringType == typeof(Base)) { // 通过反射获取当前序列化对象实例,判断是否为Derived类型 var context = JsonSerializerContext.Current; if (context?.RootElement.Value is Derived) { property.IgnoreCondition = JsonIgnoreCondition.WhenWritingDefault; } else { property.IgnoreCondition = JsonIgnoreCondition.Never; } } return property; } }
注册ContractResolver:
builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.ContractResolver = new BaseDerivedContractResolver(); });
此方式仅需调整序列化配置,无需修改业务逻辑,但需确保上下文对象类型判断准确。
3. 控制器中动态选择序列化选项
若不想全局配置,可在控制器返回时根据baseOrDerived参数动态切换序列化规则:
定义两个序列化选项:
private static readonly JsonSerializerOptions _baseSerializeOptions = new() { DefaultIgnoreCondition = JsonIgnoreCondition.Never }; private static readonly JsonSerializerOptions _derivedSerializeOptions = new() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault };
修改控制器Action:
[HttpGet] public async Task<IActionResult> GetStuff(bool baseOrDerived, ...) { var model = await _bl.GetModel(baseOrDerived, ...); return baseOrDerived ? Json(model, _derivedSerializeOptions) : Json(model, _baseSerializeOptions); }
此方式适合单一场景,需确保model中Properties集合的对象类型与baseOrDerived参数一致。
内容的提问来源于stack exchange,提问作者Sergii Kudriavtsev

