如何使用 System.Text.Json 处理可为空的引用类型?
Posted
技术标签:
【中文标题】如何使用 System.Text.Json 处理可为空的引用类型?【英文标题】:How to deal with nullable reference types with System.Text.Json? 【发布时间】:2020-02-02 14:07:58 【问题描述】:我已将我的项目升级到 netcore 3.0,我正在重构一个项目以使用新的可为空引用类型功能,但由于以下问题很快就卡住了。
假设我使用了一个返回以下 JSON 的 REST api:
"Name": "Volvo 240",
"Year": 1989
此 api 始终返回名称/年份,因此它们不可为空。
我会使用这个简单的类进行反序列化:
public class Car
public string Name get; set;
public int Year get; set;
我会使用新的 System.Text.Json
将其反序列化为 Car
实例
var car = JsonSerializer.Deserialize<Car>(json);
这一切都有效,但是当启用可空引用类型时,我在Car
类中收到警告,Name
被声明为不可空但可以为空。我明白为什么我会得到这个,因为可以在不初始化 Name
属性的情况下实例化这个对象。
所以理想情况下Car
应该是这样的:
public class Car
public string Name get;
public int Year get;
public Car(string name, int year)
Name = name;
Year = year;
但这不起作用,因为System.Text.Json
序列化程序不支持带参数的构造函数。
所以我的问题是:我将如何声明 Car
以使 Name
不可为空并使其与 System.Text.Json
一起使用而不会收到“不可为空”警告?`
我不想让它可以为空,因为在启用可以为空的引用类型时,我基本上必须对所有内容进行空检查,并且由于我的示例中的 REST API 说它们总是被提供,它们不应该可以为空.
【问题讨论】:
【参考方案1】:更新
.NET 5 的System.Text.Json
现在支持参数化构造函数,所以这应该不再是问题了。
见https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-immutability?pivots=dotnet-5-0
下面的旧答案
在阅读了msdocs 之后,我发现了如何解决这个问题。
所以在System.Text.Json
无法在其构造函数中实例化带有参数的类之前,Car
类必须如下所示:
public class Car
public string Name get; set; = default!;
public int Year get; set;
【讨论】:
就我个人而言,我宁愿使用= "";
,否则你基本上是在说(在我看来)“这个属性永远不会为空(开玩笑)” .
或者使用 JSON.NET。 System.Text.Json还没有完全替代。 .至于= default
……还没有。当我在 2019 年 10 月读到这篇文章时,我想You are setting this to null!
。也许明年。
这类似于微软推荐的实体对象。 EF 团队实际上建议使用null!
作为一种说法,“我知道这绝不是null
。”尽管我喜欢这种可以为空的东西的潜力,但这确实感觉有点不对劲......
@MattJacobi System.Text.Json
很乐意将null
反序列化为不可为空的字符串属性;至少在默认情况下,不管反序列化类的 #nullable
状态如何(我刚刚尝试过)——是否有一些选项可以触发这种更严格的“我的意思是可空性”模式?
(对于 System.Text.Json,默认情况下,通过属性省略显式 null 和隐式 null 都不是可空性错误)【参考方案2】:
更新
如果您使用net5
,请使用@langen 指出的现在提供的参数化构造函数支持。下面的其他内容仍然有用。
原创
稍微另类的方法。 System.Text.Json
使用私有无参数构造函数似乎没有问题。所以你至少可以做到以下几点:
public class Car
public string Name get; set;
public int Year get; set;
// For System.Text.Json deserialization only
#pragma warning disable CS8618 // Non-nullable field is uninitialized.
private Car()
#pragma warning restore CS8618
public Car(string name, int year)
Name = name
?? throw new ArgumentNullException(nameof(name));
Year = year;
好处是:
从您自己的代码中初始化对象必须通过公共 ctor。 您无需对每个属性都执行 = null!;
。
使用 S.T.Json 和可为空的引用类型的缺点:
S.T.Json 仍然需要属性的设置器在反序列化期间实际设置值。我尝试使用私有对象,但不行,所以我们仍然无法获得不可变对象...【讨论】:
仅供参考,System.Text.Json 和 Json.Net 都支持参数化构造函数,但它们不支持可为空的引用类型。使用您的示例代码,两个反序列化器都会很乐意将Name
属性设置为null
,这是一个很大的问题。
空值检查并加入 ctor 总是一个好主意,因为你不能真正信任别人,好点。
在 ctor 中抛出异常会导致 ASP.net 返回 500,这不是很好。理想的行为是 ASP.net 返回一个 4XX 并告诉客户端出了什么问题。
公平地说,最初的问题并不是关于模型绑定,这总是有点痛苦。 FWIW,ASP.NET Core 6 现在考虑模型绑定/验证中的可空性并自动返回 400 Bad Request。不确定是否将 ctor 投入使用会导致 500 仍然存在。【参考方案3】:
另一种选择,对于那些想要处理具有有意义异常的缺失属性的人:
using System;
public class Car
private string? name;
private int? year;
public string Name
get => this.name ?? throw new InvalidOperationException($"nameof(this.Name) was not set.");
set => this.name = value;
public int Year
get => this.year ?? throw new InvalidOperationException($"nameof(this.Year) was not set.");
set => this.year = value;
【讨论】:
以上是关于如何使用 System.Text.Json 处理可为空的引用类型?的主要内容,如果未能解决你的问题,请参考以下文章
如何在反序列化之前使用 System.Text.Json 验证 JSON
如何在 System.Text.Json 中使用 JsonConstructor 属性
如何使用 JsonConverter 在 System.Text.Json.JsonSerializer.Serialize() 中排除属性被序列化