使用 Newtonsoft JSON 的 ObjectCreationHandling 的说明?

Explanation for ObjectCreationHandling using Newtonsoft JSON?

我正在追踪一个错误,我注意到 Newtonsoft JSON 会将项目附加到已在默认构造函数中初始化的 List<>。我在 C# 聊天中做了更多挖掘并与一些人讨论,我们注意到此行为并不适用于所有其他集合类型。

https://dotnetfiddle.net/ikNyiT

using System;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Collections.ObjectModel;

public class TestClass
{
    public Collection<string> Collection = new Collection<string>(new [] { "ABC", "DEF" });
    public List<string> List = new List<string>(new [] { "ABC", "DEF" });
    public ReadOnlyCollection<string> ReadOnlyCollection = new ReadOnlyCollection<string>(new [] { "ABC", "DEF" });
}

public class Program
{
    public static void Main()
    {
        var serialized = @"{
            Collection: [ 'Goodbye', 'AOL' ],
            List: [ 'Goodbye', 'AOL' ],
            ReadOnlyCollection: [ 'Goodbye', 'AOL' ]
        }";


        var testObj = JsonConvert.DeserializeObject<TestClass>(serialized);

        Console.WriteLine("testObj.Collection: " + string.Join(",", testObj.Collection));
        Console.WriteLine("testObj.List: " + string.Join(",", testObj.List));
        Console.WriteLine("testObj.ReadOnlyCollection: " + string.Join(",", testObj.ReadOnlyCollection));
    }
}

输出:

testObj.Collection: ABC,DEF
testObj.List: ABC,DEF,Goodbye,AOL
testObj.ReadOnlyCollection: Goodbye,AOL

如您所见,Collection<> 属性不受反序列化的影响,List<> 被追加到 ReadOnlyCollection<> 被替换。这是有意的行为吗?原因是什么?

它基本上归结为类型实例化和 ObjectCreationHandling 设置。 ObjectCreationHandling

一共有三个设置

Auto 0 Reuse existing objects, create new objects when needed.
Reuse 1 Only reuse existing objects.
Replace 2 Always create new objects.

默认为autoLine 44)。

Auto 只有在确定当前类型是否有一个 TypeInitializer 为 null 的一系列检查后才会被覆盖。那时它检查是否有无参数构造函数。

///
/// Create a factory function that can be used to create instances of a JsonConverter described by the
/// argument type.
/// The returned function can then be used to either invoke the converter's default ctor, or any
/// parameterized constructors by way of an object array.
///

基本上它的作用是这样的(看起来是 6 类 中大约 1500 行代码)。

ObjectCreationHandling och = ObjectCreationHandling.Auto;
if( typeInitializer == null )
{
 if( parameterlessConstructor )
 {
  och = ObjectCreationHandling.Reuse;
 }
 else
 {
  och = ObjectCreationHandling.Replace;
 }
}

此设置是 JsonSerializerSettings 的一部分,它由 DeserializeObject 的访问者模式构造函数内部组成。如上所示,每个设置都有不同的作用。

回到 List、Collection 和 ReadOnlyCollection,我们将查看每个的条件语句集。

列表

testObj.List.GetType().TypeInitializer == null 为假。结果,List 收到默认值 ObjectCreationHandling.Auto 并且在反序列化期间使用 testObj 实例的实例化列表,以及使用 serialized 字符串实例化的新列表。

testObj.List: ABC,DEF,Goodbye,AOL

Collection

testObj.Collection.GetType().TypeInitializer == null 为真表明没有可用的反射类型初始值设定项,因此我们进入下一个条件,即检查是否存在无参数构造函数。 testObj.Collection.GetType().GetConstructor(Type.EmptyTypes) == null 为假。结果 Collection 收到 ObjectCreationHandling.Reuse 的值( 仅重用现有的 objects)。 Collection 的实例化实例从 testObj 使用,但 serialized 字符串无法实例化。

testObj.Collection: ABC,DEF

只读Collection

testObj.ReadOnlyCollection.GetType().TypeInitializer == null 为真表明没有可用的反射类型初始值设定项,因此我们进入下一个条件,即检查是否存在无参数构造函数。 testObj.ReadOnlyCollection.GetType().GetConstructor(Type.EmptyTypes) == null也是如此。结果 ReadOnlyCollection 收到 ObjectCreationHandling.Replace 的值( 总是创建新的 objects)。仅使用 serialized 字符串中的实例化值。

testObj.ReadOnlyCollection: Goodbye,AOL

虽然这个问题已经解决了,但我本来想 post 这个答案是对一个重复问题的回答,但是那个问题已经结束了,所以我在这里发布我的答案,因为它包含一些内部调查。

因为 Json.NET 是开源的,我们可以幸运地追根究底:-) .

如果您检查 Json.NET 源,您可以找到处理反序列化 (complete source here) 的 class JsonSerializerInternalReader。这个class有个方法SetPropertyValue,在新创建的对象上设置反序列化值(代码略):

private bool SetPropertyValue(JsonProperty property, ..., object target)
{
    ...
    if (CalculatePropertyDetails(
          property, 
          ...,
          out useExistingValue,
          ... ))
    {
        return false;
    }

    ...

    if (propertyConverter != null && propertyConverter.CanRead)
    {
        ...
    }
    else
    {
        value = CreateValueInternal(
           ...,
           (useExistingValue) ? currentValue : null);
    }

    if ((!useExistingValue || value != currentValue)
        && ShouldSetPropertyValue(property, value))
    {
        property.ValueProvider.SetValue(target, value);
        ...    
        return true;
    }
    return useExistingValue;
}

如您所见,有一个布尔标志 useExistingValue 决定是否重复使用或替换现有值。

CalculatePropertyDetails 方法内部是以下代码段:

        if ((objectCreationHandling != ObjectCreationHandling.Replace)
            && (tokenType == JsonToken.StartArray || tokenType == JsonToken.StartObject)
            && property.Readable)
        {
            currentValue = property.ValueProvider.GetValue(target);
            gottenCurrentValue = true;

            if (currentValue != null)
            {
                ...

                useExistingValue = (
                   !propertyContract.IsReadOnlyOrFixedSize &&
                   !propertyContract.UnderlyingType.IsValueType());
            }
        }

对于 List<T> 底层集合,IsReadOnlyOrFixedSize returns falseIsValueType() returns false -因此,潜在的现有价值被重用。

对于ArrayIsValueType()也是false,但是IsReadOnlyOrFixedSizetrue,原因很明显,因此useExistingValue标志设置为 false 并且 SetPropertyValue 方法中的 CreateValueInternal 调用接收到一个 null 引用,该引用指示不重用现有值,而是创建一个新值,然后在新实例上设置。

如前所述,可以使用 ObjectCreationHandling.Replace 更改此行为,因为在 CalculatePropertyDetails 方法中设置 useExistingValue 之前会检查此行为。