Anton Tolstov
Anton Tolstov

Reputation: 190

Json.NET does not deserialize property with custom getter and immutable type

I'm using Newtonsoft.Json (a.k.a. Json.Net) for serialization of my class:

public class ClassWithCustomProperty
{
    private ImmutableValue value;
    public ImmutableValue Value
    {
        get => value = (value ?? new ImmutableValue(0));
        set => this.value = value;
    }
}

public class ImmutableValue
{
    public readonly int Value;
    public ImmutableValue(int value) => Value = value;
}

But when I serialize and deserialize the object I don't receive the expected value:

public static class Program
{
    public static void Main()
    {
        var json = JsonConvert.SerializeObject(new ClassWithCustomProperty
        {
            Value = new ImmutableValue(42)
        });
        // JSON is {"Value": {"Value": 42}} as expected

        var obj = JsonConvert.DeserializeObject<ClassWithCustomProperty>(json);
        Console.WriteLine(obj.Value?.Value); 
        // But after deserialization obj.Value.Value is 0, instead of 42
    }
}

What is wrong, or how to change this behavior?

I noticed that if I serialize and deserialize an ImmutableValue by itself, it works fine:

var json = JsonConvert.SerializeObject(new ImmutableValue(42));
var obj = JsonConvert.DeserializeObject<ImmutableValue>(json);
Console.WriteLine(obj.Value);  // 42

Upvotes: 1

Views: 1002

Answers (1)

Brian Rogers
Brian Rogers

Reputation: 129697

The culprit is this line in your ClassWithCustomProperty class:

    get => value = (value ?? new ImmutableValue(0));

Json.Net's default behavior is to reuse existing objects rather than replace them. So when it is time to deserialize the ImmutableValue on ClassWithCustomProperty, the serializer first checks whether there is an existing value. The accessor (above) finds that there isn't, so it creates one with value 0, which is returned to the serializer. The serializer then attempts to reuse this existing object, but since it is readonly, there is no way to set the value 42 on it. So the value remains zero.

There is an ObjectCreationHandling setting which can be used to change this behavior. If you set it to Replace, it will work the way you want. Try like this:

var settings = new JsonSerializerSettings 
{ 
    ObjectCreationHandling = ObjectCreationHandling.Replace 
};
var obj = JsonConvert.DeserializeObject<ClassWithCustomProperty>(json, settings);

Fiddle: https://dotnetfiddle.net/UVDMsL

Upvotes: 3

Related Questions