Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deal with nullable reference types with System.Text.Json?

I have upgraded my project to netcore 3.0 and I am in the middle of refactoring a project to use the new nullable references types feature, but got stuck pretty quickly because of the following issue.

Lets say I consume a REST api which returns the following JSON:

{
  "Name": "Volvo 240",
  "Year": 1989
}

This api always returns the name/year, so they are non-nullable.

I would use this simple class for deserialization:

public class Car
{
    public string Name {get; set;}
    public int Year {get; set;}
}

And I would deserialize this to a Car instance using the new System.Text.Json

var car = JsonSerializer.Deserialize<Car>(json);

This all works, but when enabling nullable reference types I get a warning in the Car class that Name is declared as non-nullable but can be null. I understand why I get this since it is possible to instantiate this object without initializing the Name property.

So ideally Car should look like this:

public class Car
{
    public string Name { get; }
    public int Year { get; }

    public Car(string name, int year)
    {
        Name = name;
        Year = year;
    }
}

But this doesn't work because System.Text.Json serializer doesn't support constructors with parameters.

So my question is: How would I declare Car so that Name is non-nullable and get it to work with System.Text.Json without getting "non-nullable" warning?`

I don't want to make it nullable because I would have to do null-checks on basically everything when enabling nullable reference types, and since the REST API in my example says that they are always provided they shouldn't be nullable.

like image 494
langen Avatar asked Oct 04 '19 08:10

langen


People also ask

Can we create nullable type based on reference type?

In a nullable aware context: A variable of a reference type T must be initialized with non-null, and may never be assigned a value that may be null . A variable of a reference type T? may be initialized with null or assigned null , but is required to be checked against null before de-referencing.

Should I use Nullable reference types?

Although using nullable reference types can introduce its own set of problems, I still think it's beneficial because it helps you find potential bugs and allows you to better express your intent in the code. For new projects, I would recommend you enable the feature and do your best to write code without warnings.

What is the point of Nullable reference types?

Nullable reference types refers to a group of features introduced in C# 8.0 that you can use to minimize the likelihood that your code causes the runtime to throw System. NullReferenceException.

What is JsonIgnore in C#?

Ignore individual properties You can specify conditional exclusion by setting the [JsonIgnore] attribute's Condition property. The JsonIgnoreCondition enum provides the following options: Always - The property is always ignored. If no Condition is specified, this option is assumed.


2 Answers

UPDATE

System.Text.Json for .NET 5 now supports parameterized constructors, so this should not be a problem anymore.

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

Old answer below

After reading the msdocs I found out how I could solve this issue.

So until System.Text.Json cannot instantiate classes with parameters in their constructor, the Car class will have to look like this:

public class Car
{
    public string Name { get; set; } = default!;
    public int Year { get; set; }
}
like image 140
langen Avatar answered Sep 28 '22 05:09

langen


Update If you're on net5, use the parameterized constructor support now offer as @langen points out. Else below can still be useful.

Original Slightly alternative approach. System.Text.Json appears to have no problems using a private parameterless constructor. So you can at least do the following:

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

Benefits being:

  • Init of the object from your own code must be through the public ctor.
  • You don't need to do = null!; on each property.

Remaining downside with S.T.Json and nullable reference types:

  • S.T.Json still requires setters on the properties to actually set the values during deserialization. I tried with private ones and it's a no go, so we still can't get an immutable object...
like image 35
benmccallum Avatar answered Sep 28 '22 07:09

benmccallum