当使用Json.net反序列化带有父子关系的对象图时,非默认构造函数的使用会打破反序列化的顺序,以便在其父对象之前反序列化子对象(构造和分配属性),从而导致空引用。
从实验中可以看出,所有非默认构造函数对象只在所有默认构造函数对象之后被实例化,奇怪的是,它似乎与序列化(父级之前的子对象)相反。
这将导致应该具有对其父对象(并正确序列化)的引用的“子”对象被反序列化为空值。
这似乎是一个非常常见的场景,所以我想知道我是否遗漏了什么?
有没有改变这种行为的环境?它是为其他场景设计的吗?除了全面创建默认构造函数之外,还有其他解决办法吗?
使用LINQPad或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产出:
{
"$id": "1",
"$type": "Root",
"Middle": {
"$id": "2",
"$type": "Middle",
"Root": {
"$ref": "1"
},
"Child": {
"$id": "3",
"$type": "Child",
"Middle": {
"$ref": "2"
}
}
}
}具有非默认构造函数的中间输出:
Root
Child
Middle
Child.Middle = null具有默认构造函数的中东输出:
Root
Middle
Child
Child.Middle = Middle发布于 2016-04-26 17:07:47
您需要使用与序列化相同的反序列化设置。尽管如此,您似乎在Json.NET中遇到了一个错误或限制。
发生这种情况的原因如下。如果您的Middle类型没有公共的无参数构造函数,但是有一个带有参数的公共构造函数,JsonSerializerInternalReader.CreateObjectUsingCreatorWithParameters()将调用该构造函数,通过名称将构造函数参数与JSON属性匹配,并对缺少的属性使用默认值。然后,所有剩余的未使用的JSON属性都将被设置为该类型。这使得只读属性的反序列化成为可能.例如,如果我将只读属性Foo添加到您的Middle类中:
public class Middle
{
readonly int foo;
public int Foo { get { return foo; } }
public Middle(int Foo) { this.foo = Foo; "Middle".Dump(); }
public Root Root { get; set; }
public Child Child { get; set; }
}Foo的值将被成功反序列化。( JSON属性名称与构造函数参数名称的匹配在文档中显示为这里,但没有得到很好的解释。)
但是,这个功能似乎会干扰PreserveReferencesHandling.All。由于CreateObjectUsingCreatorWithParameters()完全反序列化正在构造的对象的所有子对象,以便将这些子对象传递到其构造函数中,如果子对象有一个"$ref"给它,则该引用将不会被解析,因为该对象尚未被构造。
作为解决办法,您可以将私有构造函数添加到Middle类型并设置ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor。
public class Middle
{
private Middle() { "Middle".Dump(); }
public Middle(int Foo) { "Middle".Dump(); }
public Root Root { get; set; }
public Child Child { get; set; }
}然后:
var settings = new JsonSerializerSettings
{
Formatting = Newtonsoft.Json.Formatting.Indented,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
PreserveReferencesHandling = PreserveReferencesHandling.All,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
};
var deserialized = JsonConvert.DeserializeObject<Root>(json, settings);当然,如果这样做,您就失去了反序列化Middle的只读属性(如果有的话)的能力。
你可能想在这件事上做报告问题。理论上,当使用参数化构造函数反序列化类型时,以牺牲更高内存使用率为代价,Json.NET可以:
JToken中。JsonSerializer.ReferenceResolver中。但是,如果任何构造函数参数本身对被反序列化的对象都有一个"$ref",则这看起来并不容易修复。
https://stackoverflow.com/questions/36866131
复制相似问题