Context
I have two classes:
- LiteralModel is a wrapper around a string Value property.
Expression has a string Name and a LiteralModel called Literal.
public class ExpressionModel { private string name; public string Name { get => name ?? ""; set => name = value; } private LiteralModel literal; public LiteralModel Literal { get => literal ?? new LiteralModel(); set => literal = value; } } public class LiteralModel { private string value; public string Value { get => value ?? ""; set => this.value = value; } }
All are public properties with public getters and setters, and so I would expect both of them to serialize and deserialize easily, even with the null guards, and, for the most part, they do.
The Problem
The Literal property of the ExpressionModel does not deserialize properly. Below is a minimal test that demonstrates the issue:
public void TestNewtonsoftExpressionDeserialization()
{
ExpressionModel expression = new ExpressionModel
{
Name = "test",
Literal = new LiteralModel { Value = "61" }
};
string json = JsonConvert.SerializeObject(expression);
Assert.IsTrue(json.Contains("61")); // passes
ExpressionModel sut = JsonConvert.DeserializeObject<ExpressionModel>(json);
Assert.AreEqual("test", sut.Name); // passes
Assert.AreEqual("61", sut.Literal.Value); // fails
}
As you can see, the JSON looks how I want/expect, (wrapping the string "61"), but when I deserialize that back into an ExpressionModel, the Literal test fails--it gets a LiteralModel of an empty string.
What I've Tried
If I remove the smart-ness of the Literal getter of the expression model, it behaves as expected--all tests pass. But smart properties do work on the string properties. So why not on my LiteralModel object?
Even weirder, all tests pass if I move the null-check to the setter instead of the getter like so:
public LiteralModel Literal
{
get => literal;
set => literal = value ?? new LiteralModel();
}
Conclusion
In short, nothing phases the serializer, and smart setters are fine, but smart getters break deserialization, except for string
.
This seems like wildly arbitrary behavior. Does anyone know why this might be or if there's any way to get these classes to work as written?