使用Newtonsoft JSON解释ObjectCreationHandling?

时间:2015-01-08 20:00:19

标签: c# json.net

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

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<>并被{{1}}替换。这是预期的行为吗?是什么原因?

2 个答案:

答案 0 :(得分:9)

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

有三种设置
  

自动0重新使用现有对象,在需要时创建新对象       重用1仅重用现有对象       替换2始终创建新对象。

默认值为autoLine 44)。

自动仅在经过一系列检查后被覆盖,这些检查确定当前类型的TypeInitializer是否为空。此时它会检查是否存在无参数构造函数。

  

///    ///创建一个工厂函数,可用于创建由中描述的JsonConverter实例    ///参数类型。
   ///然后可以使用返回的函数调用转换器的默认ctor或任何
   ///通过对象数组的参数化构造函数    ///

基本上它就像这样(它看起来像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实例的实例化List,以及使用serialized字符串实例化的新List。

testObj.List: ABC,DEF,Goodbye,AOL

集合

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

testObj.Collection: ABC,DEF

ReadOnlyCollection

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

testObj.ReadOnlyCollection: Goodbye,AOL

答案 1 :(得分:3)

虽然这已经解决了,但我想把这个答案原来发布给一个重复的问题,但是这个问题已经关闭了,所以我在这里发表我的答案,因为它包含了一些内部的看法。

因为Json.NET是开源的,所以我们可以幸运地找到它的根源:-)。

如果检查Json.NET源代码,可以找到处理反序列化(complete source here)的类JsonSerializerInternalReader。该类有一个方法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返回falseIsValueType()返回false - 因此会重用基础现有值。

对于ArrayIsValueType()也是false,但IsReadOnlyOrFixedSizetrue的原因显而易见,因此useExistingValue标志已设置false方法中的CreateValueInternal SetPropertyValue调用null调用会收到ObjectCreationHandling.Replace引用,该引用不是重用现有值,而是创建新值,即然后设置新实例。

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