Recently, I was tricked by Roslyn, today by Json.NET. My bloody luck ;) Let's look at the following two very simple classes. Class A has one readonly property and I had to define a special constructor to allow Json.NET to set this property. B is also simple. It has one property, this time of type A with some default value.
public class A { public string Name { get; } [JsonConstructor] public A(string name) { Name = name; } } public class B { public A Test { get; set; } = new A("Default"); }Now let's serialize an instance of B in the following way:
var serializedText = JsonConvert.SerializeObject(new B() { Test = new A("Fun") { } });In the result, as expected, we will get the following text:
{"Test":{"Name":"Fun"}}
Now let's deserialize this string:
var result = (B)JsonConvert.DeserializeObject(serializedText, typeof(B));Now question for 1 million dollars. What will be the value of A.Test.Name in a deserialized object. If you say Fun you are wrong! It will be Default!
I was surprised and I spent some time investigating this issue. Why did it happen? Well, it seems to me that when Json.NET creates a new object during deserialization and it notices that some property of this object is not null, them this default value will not be overriden. If it's a problem, there are at least 2 solutions:
- Remove a default value for this property. It doesn't matter if it is set in a constructor of via a property initializer.
- Add a special constructor. For instance, for class B it'll look as below. This constructor instructs Json.NET that Test property must be set even if a default value exists.
public class B { public A Test { get; set; } = new A("Default"); public B() {} [JsonConstructor] public B(A test) { Test = test; } }
*The picture at the beginning of the post comes from own resources and shows elephants from Warsaw zoo.
6 comments:
Isn't the problem much simpler than property construction? A.Name is written as read only. Whether the deserializer creates an instance of A to assign to B.Test or uses an existing instance (your "Default") is irrelevant. B.Test.Name is still read-only.
So I re-read your post several times (I think all the As and Bs were confusing me). I think I jumped the gun on my first comment before I fully understood the solution. More importantly, I don't think I've used the JsonConstructor attribute so I didn't realize what it was accomplishing. I've typically solve the problem you describe by just making the properties read/write.
@Peter Lanoie Thanks for the comment. Next time I will use more descriptive names for classes ;)
As to "making the properties read/write". It should help to make A.Name writable. However, it is not always possible.
I've ran into an issue with default values and Preserve References, see https://github.com/JamesNK/Newtonsoft.Json/issues/1351
Upon deserialization you should use var result = JsonConvert.DeserializeObject<B>(serializedText, new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace });
@Tyler Brinkley - Thanks, I didn't know this one.
Post a Comment