如何使用 System.Text.Json 处理可空引用类型?
How to deal with nullable reference types with System.Text.Json?
我已经将我的项目升级到 netcore 3.0,我正在重构一个项目以使用新的可空引用类型功能,但由于以下问题很快就卡住了。
假设我使用了一个 REST api,其中 returns 以下 JSON:
{
"Name": "Volvo 240",
"Year": 1989
}
这个 api 总是 returns name/year,所以它们是不可空的。
我会使用这个简单的 class 进行反序列化:
public class Car
{
public string Name {get; set;}
public int Year {get; set;}
}
我会使用新的 System.Text.Json
将其反序列化为 Car
实例
var car = JsonSerializer.Deserialize<Car>(json);
这一切都有效,但是当启用可为空的引用类型时,我在 Car
class 中收到一条警告,即 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
不可为 null 并让它与 System.Text.Json
一起工作而不会收到 "non-nullable" 警告? `
我不想让它可以为空,因为在启用可为空的引用类型时,我基本上必须对所有内容进行空检查,而且因为我的示例中的 REST API 表示它们总是被提供它们不应该为空。
更新
System.Text.Json
for .NET 5 现在支持参数化构造函数,所以这应该不再是问题了。
下面是旧答案
阅读 msdocs 后,我发现了如何解决这个问题。
因此,直到 System.Text.Json
无法在其构造函数中使用参数实例化 classes,Car
class 将必须如下所示:
public class Car
{
public string Name { get; set; } = default!;
public int Year { get; set; }
}
更新
如果您使用 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;
}
}
好处是:
- 从您自己的代码初始化对象必须通过 public 构造函数。
- 您不需要对每个 属性 执行
= null!;
。
S.T.Json 和可空引用类型的剩余缺点:
- S.T.Json 仍然需要属性的设置器在反序列化期间实际设置值。我尝试了私有对象,但不行,所以我们仍然无法获得不可变对象...
另一种选择,适用于那些想要处理缺失属性并具有有意义异常的人:
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;
}
}
我已经将我的项目升级到 netcore 3.0,我正在重构一个项目以使用新的可空引用类型功能,但由于以下问题很快就卡住了。
假设我使用了一个 REST api,其中 returns 以下 JSON:
{
"Name": "Volvo 240",
"Year": 1989
}
这个 api 总是 returns name/year,所以它们是不可空的。
我会使用这个简单的 class 进行反序列化:
public class Car
{
public string Name {get; set;}
public int Year {get; set;}
}
我会使用新的 System.Text.Json
Car
实例
var car = JsonSerializer.Deserialize<Car>(json);
这一切都有效,但是当启用可为空的引用类型时,我在 Car
class 中收到一条警告,即 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
不可为 null 并让它与 System.Text.Json
一起工作而不会收到 "non-nullable" 警告? `
我不想让它可以为空,因为在启用可为空的引用类型时,我基本上必须对所有内容进行空检查,而且因为我的示例中的 REST API 表示它们总是被提供它们不应该为空。
更新
System.Text.Json
for .NET 5 现在支持参数化构造函数,所以这应该不再是问题了。
下面是旧答案
阅读 msdocs 后,我发现了如何解决这个问题。
因此,直到 System.Text.Json
无法在其构造函数中使用参数实例化 classes,Car
class 将必须如下所示:
public class Car
{
public string Name { get; set; } = default!;
public int Year { get; set; }
}
更新
如果您使用 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;
}
}
好处是:
- 从您自己的代码初始化对象必须通过 public 构造函数。
- 您不需要对每个 属性 执行
= null!;
。
S.T.Json 和可空引用类型的剩余缺点:
- S.T.Json 仍然需要属性的设置器在反序列化期间实际设置值。我尝试了私有对象,但不行,所以我们仍然无法获得不可变对象...
另一种选择,适用于那些想要处理缺失属性并具有有意义异常的人:
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;
}
}