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

在序列化派生类型集合时如何正确使用[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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 21:30:31