Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/258.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 非默认构造函数的使用打破了Json.net中的反序列化顺序_C#_Serialization_Json.net - Fatal编程技术网

C# 非默认构造函数的使用打破了Json.net中的反序列化顺序

C# 非默认构造函数的使用打破了Json.net中的反序列化顺序,c#,serialization,json.net,C#,Serialization,Json.net,使用Json.net对具有父子关系的对象图进行反序列化时,使用非默认构造函数会打破反序列化顺序,使子对象在其父对象之前进行反序列化(构造并分配属性),从而导致空引用 从实验来看,似乎所有非默认构造函数对象都是在所有默认构造函数对象之后实例化的,奇怪的是,它似乎与序列化的顺序相反(子对象先于父对象) 这会导致应该引用其父对象(并正确序列化)的“子”对象改为使用空值反序列化 这似乎是一个非常常见的情况,所以我想知道我是否错过了什么 是否有更改此行为的设置?它是为其他场景设计的吗?除了全面创建默认构造

使用Json.net对具有父子关系的对象图进行反序列化时,使用非默认构造函数会打破反序列化顺序,使子对象在其父对象之前进行反序列化(构造并分配属性),从而导致空引用

从实验来看,似乎所有非默认构造函数对象都是在所有默认构造函数对象之后实例化的,奇怪的是,它似乎与序列化的顺序相反(子对象先于父对象)

这会导致应该引用其父对象(并正确序列化)的“子”对象改为使用空值反序列化

这似乎是一个非常常见的情况,所以我想知道我是否错过了什么

是否有更改此行为的设置?它是为其他场景设计的吗?除了全面创建默认构造函数之外,还有其他变通方法吗

LINQPad或的一个简单示例:

中间有非默认构造函数的输出:

Root
Child
Middle
Child.Middle = null
Root
Middle
Child
Child.Middle = Middle
中间带默认构造函数的输出:

Root
Child
Middle
Child.Middle = null
Root
Middle
Child
Child.Middle = Middle

反序列化需要使用与序列化相同的设置。也就是说,您似乎在Json.NET中遇到了错误或限制

发生这种情况的原因如下。如果您的
Middle
类型没有公共无参数构造函数,但有一个带参数的公共构造函数, 将调用该构造函数,按名称将构造函数参数与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
类型并设置:

然后:

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);
var设置=新的JsonSerializerSettings
{
格式化=Newtonsoft.Json.Formatting.Indented,
ReferenceLoopHandling=ReferenceLoopHandling.Ignore,
PreserveReferencesHandling=PreserveReferencesHandling.All,
ConstructorHandling=ConstructorHandling.AllowNonPublicDefaultConstructor,
};
var deserialized=JsonConvert.DeserializeObject(json,设置);
当然,如果这样做,您将失去对
Middle
的只读属性(如果有)进行反序列化的能力

你可能想知道这件事。理论上,当使用参数化构造函数反序列化类型时,Json.NET可能会以更高的内存使用率为代价:

  • 将所有子JSON属性加载到中间
    JToken
  • 仅将那些必需的参数反序列化为构造函数参数
  • 构造对象
  • 将对象添加到
  • 反序列化并设置其余属性

但是,如果任何构造函数参数本身对要反序列化的对象有一个“$ref””,这似乎不容易修复。

@JonSkeet谢谢,添加了!您使用的是Json.NET的哪个版本?@dbc它出现在最新的nuget包(8.0.3)中,我在5.0.8上测试了它,并遇到了相同的问题-这让我认为它是设计的(尽管我不理解).include a note Fix-Fixed使用只读属性保留对象引用,但在该版本或更高版本中它并没有被修复。相关:感谢您对发生这种情况的原因所作的精彩解释。是的,如果在子级反序列化之前(基于名称+类型)选择构造函数,然后在构造之前只反序列化所需的子级,这是有意义的——尽管可能会有一些开销。我将报告一个问题,可能还有一个请求。非常感谢。
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);