如何使用 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 现在支持参数化构造函数,所以这应该不再是问题了。

https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-immutability?pivots=dotnet-5-0

下面是旧答案

阅读 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;
    }
}