2

In the following sample, both foo and deserializedFoo variables have a fully-populated Values property. A private, no-op constructor is given the [JsonConstructor] attribute to avoid constructor value mapping from the public constructor.

In spite of this, it appears that JSON.NET is able to set the value of the get-only property if its backing field is assigned to an empty list during construction.

I am unable to find any mention of this behaviour in the JSON.NET documentation. Can anyone explain this behaviour?

void Main()
{
    var foo = new Foo(new [] { "One", "Two", "Three" });
    var json = JsonConvert.SerializeObject(foo);
    var deserializedFoo = JsonConvert.DeserializeObject<Foo>(json);
}

class Foo
{
    private readonly List<string> _values;

    public Foo(IEnumerable<string> values)
    {
        _values = values.ToList();
    }

    [JsonConstructor]
    private Foo()
    {
        _values = new List<string>();
    }

    public IEnumerable<string> Values => _values;
}
pixelbadger
  • 1,556
  • 9
  • 24

1 Answers1

4

I'm not sure where this is documented (if at all) but during deserialization of an object, if a reference-type member is already allocated, Json.NET will re-use the existing member instance rather than allocating a new one. Then it will populate the JSON into the member using the actual, concrete type of the member's value -- not the declared type. In your case the actual, concrete type of the object returned by Values is List<string> -- which Json.NET is able to populate.

If I change Values to actually return a read-only enumerable like so:

public IEnumerable<string> Values => _values.AsReadOnly();

Then Values is not populated -- demo fiddle #1 here.

On the other hand if I change the declared type of Values to be object like so:

public object Values => _values;

It is populated. Demo fiddle #2 here.

And as a final demonstration, if I modify your Foo model to have some additional pre-allocated, read-only property Bar that is only declared as object like so:

class Foo
{
    private readonly List<string> _values;
    private readonly Bar _bar = new Bar();

    public Foo(IEnumerable<string> values)
    {
        _values = values.ToList();
    }

    [JsonConstructor]
    private Foo()
    {
        _values = new List<string>();
    }

    public IEnumerable<string> Values { get { return _values; } }

    public object Bar { get { return _bar; } }
}

public class Bar
{
    public string Property1 { get; set; }
}

Then the actual object referred to by the property will be successfully populated:

var json = @"{""Values"":[""One"",""Two"",""Three""],""Bar"":{""Property1"":""Test""}}";
var deserializedFoo = JsonConvert.DeserializeObject<Foo>(json);
Assert.IsTrue(deserializedFoo.Values.Count() == 3 && ((Bar)deserializedFoo.Bar).Property1 == "Test"); // No Assert

Demo fiddle #3 here.

So what are your options, if you don't want this behavior?

Firstly, you could wrap your collection in a read-only wrapper as shown above. This is not a bad idea in general, because it prevents consumers of your Foo type from simply casting Values to a List<T> and modifying it.

Secondly, you could deserialize using ObjectCreationHandling.Replace. When this setting is enabled, Json.NET will always allocate new values for pre-allocated read/write properties -- and skip over pre-allocated read-only properties. Demo fiddle #4 here. Note this prevents population of read-only member referring to any .Net reference type, not just collections.

Thirdly, you could adopt the approach from this answer to Serialize Property, but Do Not Deserialize Property in Json.Net by Pavlo Lissov and create a custom contract resolver that sets JsonProperty.ShouldDeserialize to a predicate returning false for properties that should not get deserialized.

dbc
  • 104,963
  • 20
  • 228
  • 340