环境:.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
集合中的Properties
对象的键,但是如果要序列化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);
}
}
返回对象(基本对象或派生对象)的类型需要依赖于我从API客户机获得的输入参数baseOrDerived
。这意味着,为了使用泛型,我需要通过控制器传递类型参数。此外,我必须向IBusinessLogic/BusinessLogic
对引入相同的参数,而不是简单地从DI中获取IBusinessLogic
实例,我必须在那里获得一个ServiceProvider
实例,在操作中创建一个作用域,并动态地构造IBusinessLogic
的模板化实例。考虑到这并不是我想要的唯一的类,这在我看来是一个真正的过头。
发布于 2022-11-16 16:56:44
在序列化多态类型层次结构时,System.Text.Json根据声明的而不是其实际的具体类型,为每个要序列化的对象派生一个契约。因此,如果我创建一个Derived
实例并将其序列化为Derived
和Base
,则得到不同的结果:Derived
的{}
,Base
的{"Prop1":null,"Prop2":null,"Prop3":null}
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 {}
有关详细信息,请参阅序列化派生类的属性。
演示小提琴#1 这里。
因此,假设您只需要序列化,您可以通过为Properties
添加代理项IEnumerable<object>
属性来修改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
的内容将被序列化为Base
或Derived
,这取决于它们的实际具体类型。
演示小提琴#2 这里。
顺便说一句,您对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 这里。
顺便说一句,in .NET 7您将不需要使用代理项object
属性,您将能够通过将[JsonDerivedType(typeof(Derived))]
添加到Base
来指示Derived
类型的对象应该序列化为Base
。
[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
:
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
的需求。使用此模型,Prop1
、Prop2
和Prop3
只有在设置显式时才会序列化,因此您将能够执行以下操作:
// 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 这里。
发布于 2022-11-16 14:37:09
一种选择是使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 }}};
发布于 2022-11-16 15:34:07
假设您正在使用System.Text.Json
,您是否尝试过配置序列化器选项以跳过序列化空属性?这些选项可用于其他json序列化库(如Newtonsoft)。
JsonSerializerOptions options = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
如果您想要序列化某些属性,只需使用可重定向的默认值填充即可。
对于[JsonIgnore]
alternative,请看一下多态序列化
https://stackoverflow.com/questions/74462028
复制相似问题