简体   繁体   English

为什么 JsonConvert 对待默认构造函数和参数化构造函数的方式不同?

[英]Why does JsonConvert treat default constructors and parameterized constructors differently?

I was trying out Json.net's ability to serialize and deserialize dictionaries, and thanks to this post I was able to find a good solution to serializing dictionaries in a simple way.我正在尝试 Json.net 序列化和反序列化字典的能力,并且感谢这篇文章,我能够找到一个以简单方式序列化字典的好解决方案。

It was working great, but in a certain circumstance it broke in a (to me) nonsensical way such that I couldn't help but spend the next three hours debugging.它工作得很好,但在某种情况下,它以一种(对我而言)荒谬的方式破坏了,以至于我忍不住花了接下来的三个小时进行调试。

Here is the problem.这就是问题所在。 When serializing this class序列化此 class 时

public class ReferenceTesting
{
    public List<Scenario> scenarios = new List<Scenario>();
    private Dictionary<Scenario, float> _Dict = new Dictionary<Scenario, float>();
    [JsonProperty]
    public List<KeyValuePair<Scenario, float>> SerializedDict
    {
        get { return _Dict.ToList(); }
        set { _Dict = value.ToDictionary(x => x.Key, x => x.Value); }
    }
    public ReferenceTesting(int number = 0)
    {
        for (int i = 0; i < number; i++)
        {
            Scenario s1 = new Scenario();
            scenarios.Add(s1);
            _Dict.Add(s1, i);
        }
    }
    public override string ToString()
    {
        string s = "";
        for (int i = 0; i < scenarios.Count(); i++)
        {
            Scenario scenario = scenarios[i];
            s += $"scenario{i} \n";
        }
        foreach (KeyValuePair<Scenario, float> scenario in SerializedDict)
        {
            s += $"Key: {scenario.Key}, Value: {scenario.Value} \n";
        }
        return s;
    }
}

Everything works as expected, meaning when I instantiate一切都按预期工作,这意味着当我实例化时

new Reference(3);

and then serialize and deserialize, I end up with an object with as expected 3 items in the list, and 3 items in the dictionary.然后序列化和反序列化,我最终得到一个 object,列表中有 3 个项目,字典中有 3 个项目。

Output: Output:

scenario0 
scenario1 
scenario2 
Key: Scenario, Value: 0 
Key: Scenario, Value: 1 
Key: Scenario, Value: 2 

However, by adding the default constructor但是,通过添加默认构造函数

public ReferenceTesting() { }

the serialization works, writing out 3 items in list and dictionary, but deserialization does not work with the property.序列化工作,在列表和字典中写出 3 个项目,但反序列化不适用于该属性。 Meaning I end up with意思是我最终得到

scenario0 
scenario1 
scenario2 

as output.作为 output。

The big surprise with this is that the two constructors do the exact same thing - which is nothing when number = 0 (which it is when Json.net creates it, I doublechecked).最大的惊喜是这两个构造函数做了完全相同的事情——当 number = 0 时这没什么(当 Json.net 创建它时,我仔细检查了)。 So this means that the serializer has to be doing something under the hood to treat the property differently if there is or is not a default constructor.所以这意味着如果有或没有默认构造函数,序列化程序必须在后台做一些事情来区别对待属性。

How does Json.NET deserialize an object differently when it has a parameterized constructor vs. when it has a default constructor?当 Json.NET 反序列化 object 时,它具有参数化构造函数与具有默认构造函数时有何不同?

Json.NET is a streaming deserializer . Json.NET 是一个流解串器 Whenever possible it deserializes as it streams through the JSON rather than preloading the complete JSON into an intermediate representation before final deserialization.只要有可能,它就会在通过 JSON 流式传输时进行反序列化,而不是在最终反序列化之前将完整的 JSON 预加载到中间表示中。

Thus, when deserializing a JSON object with a default constructor, it first constructs the corresponding.Net object.因此,在使用默认构造函数反序列化一个JSON object时,首先构造对应的.Net object。 It then recursively populates the object's.Net members by streaming through the key/value pairs in the JSON until the end of the JSON object.然后,它通过流式传输 JSON 中的键/值对直到 JSON object 结束,递归地填充对象的.Net 成员。 For each pair encountered, it finds the corresponding.Net member.对于遇到的每一对,它都会找到相应的.Net 成员。 If the value is a primitive type, it deserializes the primitive and sets the value.如果该值是原始类型,则反序列化该原始类型并设置该值。 But if the value is a complex type (JSON object or array) it constructs the child object if necessary, sets the value back in the parent, and then populates it recursively as it continues to stream.但是,如果该值是复杂类型(JSON object 或数组),它会在必要时构造子 object,将值设置回父级,然后在继续到 ZF7B44CFAFD5C52223D5498196C8 时递归地填充它。

However, when deserializing an object with a parameterized constructor , Json.NET cannot use this streaming algorithm and instead must first fully deserialize the JSON object to an intermediate table of deserialized.Net name/value pairs, matching each JSON value to its corresponding.Net constructor argument or property by name and then deserializing to the type declared in.Net. However, when deserializing an object with a parameterized constructor , Json.NET cannot use this streaming algorithm and instead must first fully deserialize the JSON object to an intermediate table of deserialized.Net name/value pairs, matching each JSON value to its corresponding.Net constructor argument或按名称属性,然后反序列化为在.Net 中声明的类型。 Only then can the object be constructed by passing the deserialized constructor parameters into the constructor, and setting the remainder as property values.只有这样才能通过将反序列化的构造函数参数传递给构造函数,并将余数设置为属性值来构造 object。

For details on this process, see有关此过程的详细信息,请参阅

(There is a third algorithm for ISerializable objects which does not apply in your case.) ISerializable对象的第三种算法不适用于您的情况。)

Why is my surrogate public List<KeyValuePair<Scenario, float>> SerializedDict property not deserialized correctly when deserializing via a default constructor?为什么我的代理public List<KeyValuePair<Scenario, float>> SerializedDict属性在通过默认构造函数反序列化时未正确反序列化?

The reason is explained in this answer to Why are all the collections in my POCO are null when deserializing some valid json with the .NET Newtonsoft.Json component , and arises about of the specifics of Json.NET's Populate() algorithm: The reason is explained in this answer to Why are all the collections in my POCO are null when deserializing some valid json with the .NET Newtonsoft.Json component , and arises about of the specifics of Json.NET's Populate() algorithm:

  1. It calls the getter in the parent class to get the current value of the property being deserialized.它调用父 class 中的 getter 来获取正在反序列化的属性的当前值。

  2. If null, and unless a custom constructor is being used, it allocates an instance of the property's returned type (using the JsonContract.DefaultCreator method for the type).如果 null,除非使用自定义构造函数,否则它会分配属性返回类型的实例(使用该类型的JsonContract.DefaultCreator方法)。

  3. It calls the setter in the parent to set the allocated instance back into the parent.它调用父级中的设置器将分配的实例设置回父级。

  4. It proceeds to populate the instance of the type.它继续填充该类型的实例。

  5. It does not set the instance back a second time, after it has been populated.在填充实例后,它不会再次设置实例。

Thus the setter for SerializedDict is not called after the list is populated.因此,在填充列表后不会调用SerializedDict的设置器。

But when the parent class has a parameterized constructor, the property value SerializedDict is fully deserialized before its parent is constructed, so the setter is called with a fully populated surrogate list.但是当父 class 具有参数化构造函数时,属性值SerializedDict在其父构造之前完全反序列化,因此使用完全填充的代理列表调用 setter。

How can I create a surrogate collection property that works in both scenarios?如何创建适用于这两种情况的代理集合属性?

You can use an array instead of a list.您可以使用数组而不是列表。 Since an array cannot be resized, it must be fully deserialized and populated before it can be set back in the parent object:由于无法调整数组大小,因此必须完全反序列化并填充它,然后才能将其设置回父 object:

public class ReferenceTesting
{
    public KeyValuePair<Scenario, float> [] SerializedDict
    {
        get { return _Dict.ToArray(); }
        set { _Dict = value.ToDictionary(x => x.Key, x => x.Value);  }
    }
    // Remainder unchanged

You could make the array property be private if you want, by marking it with [JsonProperty] .如果需要,您可以通过使用[JsonProperty]标记数组属性来将其设为private

By the way, your current deserialization creates duplicate Scenario objects in the scenarios and _Dict collections as shown by demo fiddle #1 here .顺便说一句,您当前的反序列化会在scenarios中创建重复的Scenario对象和_Dict collections ,如这里的演示小提琴 #1 所示。

One way to fix this would be to serialize just _Dict (assuming that all scenarios are in the dictionary).解决此问题的一种方法是序列化_Dict (假设所有场景都在字典中)。 Another would be to use PreserveReferencesHandling , eg by adding [JsonObject(IsReference = true)] to Scenario :另一种方法是使用PreserveReferencesHandling ,例如将[JsonObject(IsReference = true)]添加到Scenario

[JsonObject(IsReference = true)]
public class Scenario
{
    // Remainder unchanged
}

Notes:笔记:

  • There is no standard for serialization of references in JSON. JSON 中的引用序列化没有标准。 Json.NET's implementation may not match that of other serializers. Json.NET 的实现可能与其他序列化程序不匹配。

  • PreserveReferencesHandling doesn't work for objects with parameterized constructors (see here for details), so make sure Scenario doesn't have one. PreserveReferencesHandling不适用于具有参数化构造函数的对象(有关详细信息,请参见此处),因此请确保Scenario没有。

Demo fiddle #2 here showing everything working correctly with a default constructor, and #3 here with a parameterized constructor. Demo fiddle #2 here显示了使用默认构造函数正常工作的所有内容,此处显示了#3使用参数化构造函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM