When deserializing an object graph with parent child relationships using Json.net, the use of non-default constructors breaks the order of deserialization such that child objects are deserialized (constructed and properties assigned) before their parents, leading to null references.
From experimentation it appears that all non-default-constructor objects are instantiated only after all default-constructor objects, and oddly it seems in the reverse order to the serialization (children before parents).
This causes 'child' objects that should have references to their parents (and are correctly serialized) to instead be deserialized with null values.
This seems like a very common scenario, so I wonder if I have missed something?
Is there a setting to change this behaviour? Is it somehow by design for other scenarios? Are there workarounds besides creating default constructors across the board?
A simple example with LINQPad or DotNetFiddle:
void Main()
{
var root = new Root();
var middle = new Middle(1);
var child = new Child();
root.Middle = middle;
middle.Root = root;
middle.Child = child;
child.Middle = middle;
var json = JsonConvert.SerializeObject(root, new JsonSerializerSettings
{
Formatting = Newtonsoft.Json.Formatting.Indented,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
PreserveReferencesHandling = PreserveReferencesHandling.All,
TypeNameHandling = TypeNameHandling.All,
});
json.Dump();
//I have tried many different combinations of settings, but they all
//seem to produce the same effect:
var deserialized = JsonConvert.DeserializeObject<Root>(json);
deserialized.Dump();
}
public class Root
{
public Root(){"Root".Dump();}
public Middle Middle {get;set;}
}
public class Middle
{
//Uncomment to see correct functioning:
//public Middle(){"Middle".Dump();}
public Middle(int foo){"Middle".Dump();}
public Root Root {get;set;}
public Child Child {get;set;}
}
public class Child
{
public Child(){"Child".Dump();}
public Middle Middle {get;set;}
}
JSON output:
{
"$id": "1",
"$type": "Root",
"Middle": {
"$id": "2",
"$type": "Middle",
"Root": {
"$ref": "1"
},
"Child": {
"$id": "3",
"$type": "Child",
"Middle": {
"$ref": "2"
}
}
}
}
Output with Middle having non-default constructor:
Root
Child
Middle
Child.Middle = null
Output with Middle having default constructor:
Root
Middle
Child
Child.Middle = Middle