使用 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.
默认为auto
(Line 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 false
和 IsValueType()
returns false
-因此,潜在的现有价值被重用。
对于Array
,IsValueType()
也是false
,但是IsReadOnlyOrFixedSize
是true
,原因很明显,因此useExistingValue
标志设置为 false
并且 SetPropertyValue
方法中的 CreateValueInternal
调用接收到一个 null
引用,该引用指示不重用现有值,而是创建一个新值,然后在新实例上设置。
如前所述,可以使用 ObjectCreationHandling.Replace
更改此行为,因为在 CalculatePropertyDetails
方法中设置 useExistingValue
之前会检查此行为。
我正在追踪一个错误,我注意到 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.
默认为auto
(Line 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 false
和 IsValueType()
returns false
-因此,潜在的现有价值被重用。
对于Array
,IsValueType()
也是false
,但是IsReadOnlyOrFixedSize
是true
,原因很明显,因此useExistingValue
标志设置为 false
并且 SetPropertyValue
方法中的 CreateValueInternal
调用接收到一个 null
引用,该引用指示不重用现有值,而是创建一个新值,然后在新实例上设置。
如前所述,可以使用 ObjectCreationHandling.Replace
更改此行为,因为在 CalculatePropertyDetails
方法中设置 useExistingValue
之前会检查此行为。