I have two classes:
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 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.
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();
}
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?
I suspect the problem is that Json.NET is calling your property getter, and then calling the setter on the result. For example, something like this - although obviously, via reflection:
var expression = new ExpressionModel();
expression.Name = "test";
var literal = expression.Literal;
if (literal is null)
{
// No literal - create one and set it
literal = new LiteralModel();
expression.Literal = literal;
}
// Now literal is non-null either way, so set the value.
literal.Value = "61";
With the way your code works, your Literal
getter is creating a new LiteralModel
, but then throwing it away. Leaving Json.NET aside, that's still pretty confusing. For example:
var expression = new Expression();
expression.Literal.Value = "foo";
Console.WriteLine(expression.Literal.Value); // Empty string
You could change your ExpressionModel
code to assign the newly-created LiteralModel
to the property if it creates one:
public LiteralModel Literal
{
get => literal ?? (literal = new LiteralModel());
set => literal = value;
}
That will avoid the "simple code" behaviour being so confusing - and I expect it will fix the Json.NET behaviour too.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With