【问题标题】:How to use [JsonIgnore] when serializing a collection of derived types序列化派生类型集合时如何使用 [JsonIgnore]
【发布时间】:2022-11-17 01:34:36
【问题描述】:

环境:.NET 6 WebAPI 应用程序

我有两个类,一个派生类,它们都可以用于将某个方法的输出序列化为 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; }
  ...
}

我想始终序列化 Base 集合中的 Base 对象的键,但如果我正在序列化 Derived 对象的集合,则跳过值为 null 的键。我想要实现的示例代码:

  var baseModel = new Model{ Properties = new List<Base>{ new Base { Prop1 = 1 } } };
  var serialized = JsonSerializer.Serialize(baseModel);
  // This returns '{ "properties": { "Prop1": 1, "Prop2": null, "Prop3": null }}'

  var derivedModel = new Model { Properties = new List<Derived>{ new Derived { Prop1 = 1 }}};
  // This doesn't compile because of type mismatch
  
  var derivedModel2 = new Model { Properties = new List<Base>{ (Base)new Derived { Prop1 = 1 }}}; 
  // This works, but also returns '{ "properties": { "Prop1": 1, "Prop2": null, "Prop3": null }}'
  // I need to get '{ "properties": { "Prop1": 1 } }' here

关于在哪里看有什么建议吗?

UPD:我考虑过通用类的使用,但我的模型目前以下列方式使用(简化):

public class BusinessLogic: IBusinessLogic
{
  ... // Constructor with DI etc.
  public async Task<Model> GetStuff(...)
  {
    ...
    var model = GetModelInternal(...);
    ...
    return model;
  }
}

public interface IBusinessLogic
{
  ...
  public 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 对引入相同的参数,而不是简单地从 DI 获取 IBusinessLogic 实例,我必须在那里获取一个 ServiceProvider 实例,在操作中创建一个范围并构造模板化IBusinessLogic 动态实例。鉴于这不是我想要这种行为的唯一课程,这对我来说似乎是一个真正的矫枉过正。

【问题讨论】:

  • 我想我的道路是朝着自定义ContractResolver的方向发展的。
  • 是只需要序列化,还是还要反序列化?
  • @dbc 只序列化。我正在使用具有自己类的截然不同的模型来创建此类新对象。

标签: c# system.text.json


【解决方案1】:

一种选择是使 Model 通用,如下所示:

public class Model<T> where T : Base
{
  public List<T>? Properties { get; set; }
  ...
}

现在你应该能够做到:

var derivedModel = new Model<Derived> { Properties = new List<Derived>{ new Derived { Prop1 = 1 }}};

【讨论】:

  • 我不认为我可以在不指定泛型类型参数的情况下将泛型类型指定为某个方法的返回类型,对吗?或者不使包含此方法的类也通用。我会相应地更新问题。
  • 更新了问题
【解决方案2】:

假设您使用的是System.Text.Json,您是否尝试过配置序列化程序选项以跳过序列化空属性?这些选项可用于其他 json 序列化库(如 Newtonsoft)

JsonSerializerOptions options = new()
{
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};

如果您无论如何都想序列化某些属性,只需使用合理的默认值填充它即可。

对于[JsonIgnore]替代方案,请查看polymorphic serialization

【讨论】:

  • 我也考虑过这个,但是这里的问题是我的Model类包含几十个属性,包括不同的集合和更复杂的对象,我想对不同的属性应用不同的 JsonIgnore 条件,也动态地取决于用户的输入.
  • 考虑到属性很难根据用户输入动态应用,我认为通过使 cause 中的字段可以为空并通过提供或不提供默认值来控制它们的序列化,它更易于管理和维护。
  • 田野实际上可以为 null - 我的问题是我必须将它们保持为 null 并且有时会显示具有 null 值的键,有时则不会。如果某物是一个列表,则很难提供在一般情况下意味着“隐藏它”的合理默认值。
  • 用多态序列化的提示更新了响应。它可以很好地与 JsonIgnore 属性组合......
  • 感谢您提供有关多态序列化的链接 - 我也是独立找到它的。它不会为我解决所有问题,但可能是一个好的开始 - 所以,支持你的回答。
【解决方案3】:

序列化多态类型层次结构时,System.Text.Json 为每个要序列化的对象派生契约宣布而不是它实际的、具体的类型。因此,如果我创建一个 Derived 的实例并将其序列化为 DerivedBase,我会得到不同的结果:{} 对应 Derived{"Prop1":null,"Prop2":null,"Prop3":null} 对应 Base

var model = new Derived ();

Console.WriteLine("JSON when serialized as {0}", nameof(Derived));
Console.WriteLine(JsonSerializer.Serialize<Derived>(model));  // Outputs {}

Console.WriteLine("JSON when serialized as {0}", nameof(Base));
Console.WriteLine(JsonSerializer.Serialize<Base>(model));     // Outputs {"Prop1":null,"Prop2":null,"Prop3":null} 

但是请注意,有一个重要的例外:如果要序列化的值声明为object,它将被序列化为其实际的具体类型:

Console.WriteLine("JSON when serialized as {0}", nameof(System.Object));
Console.WriteLine(JsonSerializer.Serialize<object>(model));     // Outputs {} 

详情见Serialize properties of derived classes

演示小提琴 #1 here

因此,假设你只需要序列化,您可以通过为 Properties 添加代理项 IEnumerable&lt;object&gt; 属性来修改您的 Model,如下所示:

public class Model
{
    [JsonIgnore]
    public List<Base>? Properties { get; set; }

    [JsonPropertyName(nameof(Properties))] // Surrogate property for Properties with items declared as object
    public IEnumerable<object>? SerializedProperties => Properties?.AsEnumerable();
}

Properties的内容将被序列化为BaseDerived,具体取决于它们的实际具体类型。

演示小提琴 #2 here

顺便说一句,你对Derived的定义似乎有点尴尬,因为你只是掩蔽基类的属性。您可能会考虑将它们设为虚拟,然后覆盖它们:

public class Base
{
    public virtual int? Prop1 { get; set; }
    public virtual string? Prop2 { get; set; }
    public virtual long? Prop3 { get; set; }
}

public class Derived: Base 
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
    public override int? Prop1 { get => base.Prop1; set => base.Prop1 = value; }

    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
    public override string? Prop2 { get => base.Prop2; set => base.Prop2 = value; }

    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
    public override long? Prop3 { get => base.Prop3; set => base.Prop3 = value; }
}

您将获得相同的序列化结果,而不会重复属性。

演示小提琴 #3 here

顺便,在 .NET 7 中您将不需要使用代理项object 属性,您将能够通过将[JsonDerivedType(typeof(Derived))] 添加到Base 来指示Derived 类型的对象应该被序列化,即使声明为Base 也是如此:

[JsonDerivedType(typeof(Derived))]
public class Base
{
    // Remainder unchanged

.NET 7 还将支持Conditional serialization via contract customization,这可能允许您在根本不需要类型层次结构的情况下实现所需的序列化结果。

作为备选,如果您有有时想要序列化而有时不想序列化的可空属性,您可能需要考虑使用 Optional&lt;T&gt; 模式,如 this question by Maxime Rossini 所示。该问题定义Optional&lt;T&gt;如下:

//  Converter from https://stackoverflow.com/questions/63418549/custom-json-serializer-for-optional-property-with-system-text-json/
//  With the fix for OptionalConverterInner<T>.Write() taken from https://stackoverflow.com/a/63431434/3744182
[JsonConverter(typeof(OptionalConverter))]
public readonly struct Optional<T>
{
    public Optional(T value)
    {
        this.HasValue = true;
        this.Value = value;
    }

    public bool HasValue { get; }
    public T Value { get; }
    public static implicit operator Optional<T>(T value) => new Optional<T>(value);
    public override string ToString() => this.HasValue ? (this.Value?.ToString() ?? "null") : "unspecified";
}

如果您按如下方式重新定义您的Base

public class Base
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
    public Optional<int?> Prop1 { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
    public Optional<string?> Prop2 { get; set; }
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
    public Optional<long?> Prop3 { get; set; }
}

您可以完全消除对Derived 的需要。使用此模型,Prop1Prop2Prop3 将仅在设置时序列化明确地,因此您将能够执行以下操作:

// Explicitly set null properties if baseOrDerived is false
var item = baseOrDerived ? new Base { Prop2 = "hello" } : new Base { Prop1 = null, Prop2 = "hello", Prop3 = null };

结果将是{"Prop2":"hello"}{"Prop1":null,"Prop2":"hello","Prop3":null},具体取决于baseOrDerived是否为真。

演示小提琴 #4 here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-01
    • 2011-05-07
    • 1970-01-01
    相关资源
    最近更新 更多