使用Newtonsoft JSON解释ObjectCreationHandling?
我正在追踪一个错误,我注意到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 Collection = new Collection(new [] { "ABC", "DEF" }); public List List = new List(new [] { "ABC", "DEF" }); public ReadOnlyCollection ReadOnlyCollection = new ReadOnlyCollection(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(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
有三种设置
自动0重新使用现有对象,在需要时创建新对象。
重用1仅重用现有对象。
替换2始终创建新对象。
默认值为auto
( 第44行 )。
只有在一系列检查确定当前类型是否具有null的TypeInitializer
之后,才会覆盖Auto。 此时它会检查是否存在无参数构造函数。
///
///创建一个工厂函数,可用于创建由…描述的JsonConverter的实例
///参数类型。
///然后可以使用返回的函数来调用转换器的默认ctor或any
///通过对象数组的参数化构造函数。
///
基本上它就像这样(它看起来像6行中的大约1500行代码)。
ObjectCreationHandling och = ObjectCreationHandling.Auto; if( typeInitializer == null ) { if( parameterlessConstructor ) { och = ObjectCreationHandling.Reuse; } else { och = ObjectCreationHandling.Replace; } }
此设置是JsonSerializerSettings的一部分,它们在DeserializeObject的访问者模式构造函数内部组成。 如上所示,每个设置具有不同的function。
回到List,Collection和ReadOnlyCollection,我们将查看每个条件语句的集合。
名单
testObj.List.GetType().TypeInitializer == null
为false。 因此, List
接收默认的ObjectCreationHandling.Auto,并且在反序列化期间使用testObj实例的实例化List,以及使用serialized
字符串实例化的新List。
testObj.List: ABC,DEF,Goodbye,AOL
采集
testObj.Collection.GetType().TypeInitializer == null
为true表示没有可用的reflection类型初始值设定项,因此我们转到下一个检查是否存在无参数构造函数的条件。 testObj.Collection.GetType().GetConstructor(Type.EmptyTypes) == null
为false。 结果Collection
收到ObjectCreationHandling.Reuse的值( 仅重用现有对象 )。 Collection的实例化实例用于testObj,但serialized
字符串无法实例化。
testObj.Collection: ABC,DEF
ReadOnlyCollection
testObj.ReadOnlyCollection.GetType().TypeInitializer == null
为true表示没有可用的reflection类型初始值设定项,因此我们转到下一个检查是否存在无参数构造函数的条件。 testObj.ReadOnlyCollection.GetType().GetConstructor(Type.EmptyTypes) == null
也是如此。 因此,ReadOnlyCollection接收ObjectCreationHandling.Replace的值( 始终创建新对象 )。 仅使用serialized
字符串中的实例化值。
testObj.ReadOnlyCollection: Goodbye,AOL
虽然这已经解决了,但我想把这个答案最初发布到一个重复的问题,但是这个问题已经关闭了,所以我在这里发布我的答案,因为它包含了一些内部的看法。
因为Json.NET是开源的,所以我们可以幸运地找到它的根源:-)。
如果检查Json.NET源代码,可以找到处理反序列化的类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
底层集合, IsReadOnlyOrFixedSize
返回false
并且IsValueType()
返回false
– 因此将重用基础现有值。
对于Array
, IsValueType()
也是false
,但是由于显而易见的原因, IsReadOnlyOrFixedSize
为true
,因此useExistingValue
标志设置为false
并且SetPropertyValue
方法中的CreateValueInternal
调用接收null
引用,该引用是不重用现有值的指示符,但要创建一个新的,然后在新实例上设置。
如前所述,可以使用ObjectCreationHandling.Replace
更改此行为,因为在CalculatePropertyDetails
方法中设置useExistingValue
之前会对此进行检查。