问题
I've a simple relational model with a Parent and Child relation as follows:
public class Parent
{
public Parent(int id)
{
Id = id;
}
public int Id { get; private set; }
public IList<Child> Children { get; set; } = new List<Child>();
}
public class Child
{
public Parent Parent { get; set; }
}
I create a small object graph consisting of a list of two children who share the same parent:
var parent = new Parent(1);
var child1 = new Child {Parent = parent};
var child2 = new Child {Parent = parent};
parent.Children.Add(child1);
parent.Children.Add(child2);
var data = new List<Child> {child1, child2};
Next, I serialize this using SerializeObject
:
var settings = new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.All
};
var json = Newtonsoft.Json.JsonConvert.SerializeObject(data, Formatting.Indented, settings);
As far I can see, the resulting json looks fine:
{
"$id": "1",
"$values": [
{
"$id": "2",
"Parent": {
"$id": "3",
"Id": 1,
"Children": {
"$id": "4",
"$values": [
{
"$ref": "2"
},
{
"$id": "5",
"Parent": {
"$ref": "3"
}
}
]
}
}
},
{
"$ref": "5"
}
]
}
However, when I deserialize the json I don't get the expected object because for the second child the Parent property is null, causing the second assertion to fail:
var data2 = Newtonsoft.Json.JsonConvert.DeserializeObject<IList<Child>>(json, settings);
Debug.Assert(data2[0].Parent != null);
Debug.Assert(data2[1].Parent != null);
Without the constructor of Parent
this problem does not occur and the Parent property of the second child has the expected value.
Any ideas what this could be?
回答1:
Parameterized constructors don't work well with PreserveReferencesHandling
because there is an inherent chicken-and-egg problem: the necessary information to pass to the constructor may not be loaded from the JSON by the time the deserializer needs to create the object. So there needs to be a way for it to create an empty object and then fill in the appropriate information later.
This is a known limitation and is documented:
Note:
References cannot be preserved when a value is set via a non-default constructor. With a non-default constructor, child values must be created before the parent value so they can be passed into the constructor, making tracking reference impossible. ISerializable types are an example of a class whose values are populated with a non-default constructor and won't work withPreserveReferencesHandling
.
To work around the problem with your model you could create a private, parameterless constructor in your Parent
class and mark it with [JsonConstructor]
. Then mark the Id
property with [JsonProperty]
, which will allow Json.Net to use the private setter. So you would have:
public class Parent
{
public Parent(int id)
{
Id = id;
}
[JsonConstructor]
private Parent()
{ }
[JsonProperty]
public int Id { get; private set; }
public IList<Child> Children { get; set; } = new List<Child>();
}
As an aside, since none of your Lists are shared amongst the objects, you could use PreserveReferencesHandling.Objects
instead of PreserveReferencesHandling.All
. This will make the JSON a little smaller, but will still preserve the references you care about.
Working demo here: https://dotnetfiddle.net/cLk9DM
回答2:
I haven't tested this, but I have a feeling it'll solve it if you put the settings into both serialiser and deserialiser:
var settings = new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.All
};
var json = Newtonsoft.Json.JsonConvert.SerializeObject(data, Formatting.Indented, settings);
var data2 = Newtonsoft.Json.JsonConvert.DeserializeObject<IList<Child>>(json, settings);
回答3:
Second attempt:
I reproduced the problem, and then changed the constructor of Parent
to be parameterless, and the Id
property to be settable, and then it worked. No idea why, but I guess it's some bug in the way Newtonsoft chooses how to construct things, especially as the order will be important with PreserveReferencesHandling
.
public class Parent
{
public int Id { get; set; }
public IList<Child> Children { get; set; } = new List<Child>();
}
and
var parent = new Parent { Id = 1 };
来源:https://stackoverflow.com/questions/61205328/newtonsoft-json-issue-with-deserialising-relational-model