首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在序列化派生类型的集合时如何使用[JsonIgnore]

在序列化派生类型的集合时如何使用[JsonIgnore]
EN

Stack Overflow用户
提问于 2022-11-16 14:17:44
回答 3查看 63关注 0票数 1

环境:.NET 6 WebAPI应用程序

我有两个类,一个是派生类,两个类都可以用来将某个方法的输出序列化为JSON,并将其发送给客户端。它们看起来是这样的:

代码语言:javascript
运行
复制
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对象集合的泛型模型类:

代码语言:javascript
运行
复制
public class Model
{
  public List<Base>? Properties { get; set; }
  ...
}

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

代码语言:javascript
运行
复制
  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:我考虑过泛型类的使用,但我的模型目前以以下方式(简化)使用:

代码语言:javascript
运行
复制
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);
  }
}

返回对象(基本对象或派生对象)的类型需要依赖于我从API客户机获得的输入参数baseOrDerived。这意味着,为了使用泛型,我需要通过控制器传递类型参数。此外,我必须向IBusinessLogic/BusinessLogic对引入相同的参数,而不是简单地从DI中获取IBusinessLogic实例,我必须在那里获得一个ServiceProvider实例,在操作中创建一个作用域,并动态地构造IBusinessLogic的模板化实例。考虑到这并不是我想要的唯一的类,这在我看来是一个真正的过头。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2022-11-16 16:56:44

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

代码语言:javascript
运行
复制
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,,则它将被序列化为其实际的具体类型:

代码语言:javascript
运行
复制
Console.WriteLine("JSON when serialized as {0}", nameof(System.Object));
Console.WriteLine(JsonSerializer.Serialize<object>(model));     // Outputs {} 

有关详细信息,请参阅序列化派生类的属性

演示小提琴#1 这里

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

代码语言:javascript
运行
复制
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 这里

顺便说一句,您对Derived的定义似乎有点尴尬,因为您只是将基类的属性作为掩蔽。您可以考虑将它们变为虚拟的,而不是重写它们:

代码语言:javascript
运行
复制
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 这里

顺便说一句,in .NET 7您将不需要使用代理项object属性,您将能够通过将[JsonDerivedType(typeof(Derived))]添加到Base来指示Derived类型的对象应该序列化为Base

代码语言:javascript
运行
复制
[JsonDerivedType(typeof(Derived))]
public class Base
{
    // Remainder unchanged

.NET 7还将支持通过合同定制的条件序列化,这可能允许您实现所需的序列化结果,而根本不需要类型层次结构。

作为另一种,如果您有时想序列化可空属性,有时不想序列化,则可能需要考虑使用Optional<T>模式,如这个问题 by 罗西尼中所示。这个问题对Optional<T>的定义如下:

//转换器来自https://stackoverflow.com/questions/63418549/custom-json-serializer-for-optional-property-with-system-text-json/ //,OptionalConverterInner.Write()的修复程序取自https://stackoverflow.com/a/63431434/3744182 JsonConverter(typeof(OptionalConverter))公共只读结构{公共可选(T值){ this.HasValue = true;this.Value = value;} public bool HasValue { get;} public T值{ get;} public静态隐式运算符可选(T值) =>新可选(值);公共重写字符串ToString() => this.HasValue?(this.Value?.ToString()?"null"):“未指定”;}

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

代码语言:javascript
运行
复制
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只有在设置显式时才会序列化,因此您将能够执行以下操作:

代码语言:javascript
运行
复制
// 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 这里

票数 1
EN

Stack Overflow用户

发布于 2022-11-16 14:37:09

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

代码语言:javascript
运行
复制
public class Model<T> where T : Base
{
  public List<T>? Properties { get; set; }
  ...
}

现在你应该能够做到:

代码语言:javascript
运行
复制
var derivedModel = new Model<Derived> { Properties = new List<Derived>{ new Derived { Prop1 = 1 }}};
票数 1
EN

Stack Overflow用户

发布于 2022-11-16 15:34:07

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

代码语言:javascript
运行
复制
JsonSerializerOptions options = new()
{
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};

如果您想要序列化某些属性,只需使用可重定向的默认值填充即可。

对于[JsonIgnore] alternative,请看一下多态序列化

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/74462028

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档