R
R
Ruslan2022-03-01 21:40:02
.NET
Ruslan, 2022-03-01 21:40:02

How to convert deserialization when passing to null reference types?

Hello.
I am gradually trying to use the null reference type approach in my projects, but so far it is very unusual.
Here is another doubt that caught me by surprise.
My logical chain of reasoning is set out below, I feel that something is wrong in it, but I can’t understand what exactly.
Let's assume the default values ​​I can't use. So:
1. when we declare a class, we must mark it with a ? those fields and properties that can take null, types without ? cannot accept null.

2. since we have fields without ?, they must be initialized when the class is created: either by default or through a constructor. no other way?

3. well, when creating an instance, we only have topass the initial values ​​through the constructor. accordingly, all the code that we used to assign values ​​to fields in the style

oldClass c = new oldClass();
c.field1 = "val1";
c.field2 = "val2";


or

oldClass x = new oldClass { field1 = "val1", field2 = "val2" };


Won't roll anymore.

Here I would like to hear experts in this matter. Am I understanding everything correctly up to this point?

And now another example.
Let's say I serialized a class instance to json, and then I need to deserialize it.
And I'm using Newtonsoft.Json (13.0.1)
The constructor is a method and its field assignment logic can be different there, in any case, the deserializer code is unlikely to think about the semantics of the constructor. And if so, then there are concerns about how the deserializer will be able to correctly pass values ​​​​through the constructor in order to correctly restore the fields of the original class.

I conducted an experiment and it turned out that the fears were confirmed.
source code here: https://gitlab.com/p6928/nullable-reference-types

we have a class whose constructor does not pass the input parameters in the same order in which the class properties are declared.

public class notnullableclass
{
    public notnullableclass(string notnull1, string notnull2)
    {
        this.notnull1 = notnull2;//специально так, чтобы запутать десериализатор
        this.notnull2 = notnull1;
    }
    public string notnull1 { get; set; }
    public string notnull2 { get; set; }
}


Serialization and deserialization code:

notnullableclass e1 = new notnullableclass("init1","init2");

string data1 = JsonConvert.SerializeObject(e1);

Console.WriteLine($"e1: notnull1:{e1.notnull1}; notnull2:{e1.notnull2}");
Console.WriteLine($"data1: {data1}");
            
notnullableclass e2 = JsonConvert.DeserializeObject<notnullableclass>(data1);
Console.WriteLine($"deserialized e2: notnull1:{e2.notnull1}; notnull2:{e2.notnull2}");

string data2 = "{\"notnull1\":\"init1\",\"notnull2\":\"init2\"}";
Console.WriteLine($"data2: {data2}");
notnullableclass e3 = JsonConvert.DeserializeObject<notnullableclass>(data2);
Console.WriteLine($"deserialized e3: notnull1:{e3.notnull1}; notnull2:{e3.notnull2}");


console output:

e1: notnull1:init2; notnull2:init1
data1: {"notnull1":"init2","notnull2":"init1"}
deserialized e2: notnull1:init1; notnull2:init2
data2: {"notnull1":"init1","notnull2":"init2"}
deserialized e3: notnull1:init2; notnull2:init1

As you can see, after deserialization, the field values ​​got mixed up.

Well, how after that to trust the deserialization of null reference types?

But seriously, how can you still guarantee the correct operation of the code when using the null reference type approach?
Is it still possible to guarantee that the field will not be null and not use the constructor?

Thank you for your attention.

Answer the question

In order to leave comments, you need to log in

2 answer(s)
V
Vasily Bannikov, 2022-03-01
@Razbezhkin

NRTs are just annotations and do not commit anything. Although different libraries may use the information from these annotations.


As you can see, after deserialization, the field values ​​are messed up.

And why specifically confuse it? If there is a constructor, then the serilizer will use the information from it, and you yourself decided to deceive it.
If you really need to call it that - you can mark up the parameters using attributes.
It is not necessary to initialize with a default value, although it is a good idea to make a constructor (System.Text.Json can work well with them)
For example, you can use "= null !;" as the default value - then there will be no warning.

And I am using Newtonsoft.Json (13.0.1)

I advise you to switch to STJ

types without ? cannot accept null.

Formally they can. NRT guarantees nothing.

But seriously, how can you still guarantee the correct operation of the code when using the null reference type approach?
Is it still possible to guarantee that the field will not be null and not use the constructor?

1. Switch to NRT gradually, checking all the code manually, or with some fancy static analyzers.
2. No, without a constructor with checks for null, you can't guarantee anything.
Although if the serilyizer is very smart and directly emits into private fields through emit, this will not save either.
So here's an extra reason for you to quickly cover at least the most important scenarios in your application with tests.

R
Roman, 2022-03-02
@yarosroman

The question is, why voluntarily shoot yourself in the foot?

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question