<Compile Include="System\Text\Json\Reader\Utf8JsonReader.TryGet.cs" />
<Compile Include="System\Text\Json\Serialization\ClassType.cs" />
<Compile Include="System\Text\Json\Serialization\ConverterList.cs" />
- <Compile Include="System\Text\Json\Serialization\Converters\DefaultDerivedDictionaryConverter.cs" />
- <Compile Include="System\Text\Json\Serialization\Converters\DefaultDerivedEnumerableConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\DefaultArrayConverter.cs" />
- <Compile Include="System\Text\Json\Serialization\Converters\DefaultICollectionConverter.cs" />
- <Compile Include="System\Text\Json\Serialization\Converters\DefaultIDictionaryConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\DefaultImmutableEnumerableConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\DefaultImmutableDictionaryConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\EnumConverterOptions.cs" />
<Compile Include="System\Text\Json\Serialization\JsonCamelCaseNamingPolicy.cs" />
<Compile Include="System\Text\Json\Serialization\JsonClassInfo.cs" />
<Compile Include="System\Text\Json\Serialization\JsonClassInfo.AddProperty.cs" />
- <Compile Include="System\Text\Json\Serialization\JsonClassInfo.Helpers.cs" />
<Compile Include="System\Text\Json\Serialization\JsonConverter.cs" />
<Compile Include="System\Text\Json\Serialization\JsonConverterAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\JsonConverterFactory.cs" />
Enumerable = 0x8,
// IDictionary
Dictionary = 0x10,
- // Is deserialized by passing a IDictionary to its constructor
- // i.e. immutable dictionaries, Hashtable, SortedList,
- IDictionaryConstructible = 0x20,
}
}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Collections;
-
-namespace System.Text.Json.Serialization.Converters
-{
- internal sealed class DefaultDerivedDictionaryConverter : JsonDictionaryConverter
- {
- public override object CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options)
- {
- JsonPropertyInfo collectionPropertyInfo = state.Current.JsonPropertyInfo;
- JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(collectionPropertyInfo.ElementType, options);
- return elementPropertyInfo.CreateDerivedDictionaryInstance(ref state, collectionPropertyInfo, sourceDictionary);
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Collections;
-
-namespace System.Text.Json.Serialization.Converters
-{
- internal sealed class DefaultDerivedEnumerableConverter : JsonEnumerableConverter
- {
- public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options)
- {
- JsonPropertyInfo collectionPropertyInfo = state.Current.JsonPropertyInfo;
- JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(collectionPropertyInfo.ElementType, options);
- return elementPropertyInfo.CreateDerivedEnumerableInstance(ref state, collectionPropertyInfo, sourceList);
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Collections;
-
-namespace System.Text.Json.Serialization.Converters
-{
- internal sealed class DefaultICollectionConverter : JsonEnumerableConverter
- {
- public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options)
- {
- Type enumerableType = state.Current.JsonPropertyInfo.RuntimePropertyType;
- Type elementType = state.Current.JsonPropertyInfo.ElementType;
- JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementType, options);
- return propertyInfo.CreateIEnumerableInstance(ref state, enumerableType, sourceList);
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Collections;
-
-namespace System.Text.Json.Serialization.Converters
-{
- internal sealed class DefaultIDictionaryConverter : JsonDictionaryConverter
- {
- public override object CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options)
- {
- Type dictionaryType = state.Current.JsonPropertyInfo.RuntimePropertyType;
- Type elementType = state.Current.JsonPropertyInfo.ElementType;
- JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementType, options);
- return propertyInfo.CreateIDictionaryInstance(ref state, dictionaryType, sourceDictionary);
- }
- }
-}
public override object CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options)
{
Type immutableCollectionType = state.Current.JsonPropertyInfo.RuntimePropertyType;
- Type elementType = state.Current.GetElementType();
+
+ JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo;
+ Type elementType = elementClassInfo.Type;
string delegateKey = DefaultImmutableEnumerableConverter.GetDelegateKey(immutableCollectionType, elementType, out _, out _);
- JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementType, options);
+ JsonPropertyInfo propertyInfo = elementClassInfo.PolicyProperty ?? elementClassInfo.CreateRootObject(options);
+ Debug.Assert(propertyInfo != null);
return propertyInfo.CreateImmutableDictionaryInstance(ref state, immutableCollectionType, delegateKey, sourceDictionary, options);
}
}
options.TryAddCreateRangeDelegate(delegateKey, createRangeDelegate);
}
+ public static bool IsImmutableEnumerable(Type type, out bool IsImmutableArray)
+ {
+ if (!type.IsGenericType)
+ {
+ IsImmutableArray = false;
+ return false;
+ }
+
+ switch (type.GetGenericTypeDefinition().FullName)
+ {
+ case ImmutableArrayGenericTypeName:
+ IsImmutableArray = true;
+ return true;
+ case ImmutableListGenericTypeName:
+ case ImmutableListGenericInterfaceTypeName:
+ case ImmutableStackGenericTypeName:
+ case ImmutableStackGenericInterfaceTypeName:
+ case ImmutableQueueGenericTypeName:
+ case ImmutableQueueGenericInterfaceTypeName:
+ case ImmutableSortedSetGenericTypeName:
+ case ImmutableHashSetGenericTypeName:
+ case ImmutableSetGenericInterfaceTypeName:
+ IsImmutableArray = false;
+ return true;
+ default:
+ IsImmutableArray = false;
+ return false;
+ }
+ }
+
public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options)
{
Type immutableCollectionType = state.Current.JsonPropertyInfo.RuntimePropertyType;
- Type elementType = state.Current.GetElementType();
+
+ JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo;
+ Type elementType = elementClassInfo.Type;
string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out _, out _);
- JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementType, options);
+ JsonPropertyInfo propertyInfo = elementClassInfo.PolicyProperty ?? elementClassInfo.CreateRootObject(options);
+ Debug.Assert(propertyInfo != null);
return propertyInfo.CreateImmutableCollectionInstance(ref state, immutableCollectionType, delegateKey, sourceList, options);
}
}
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text.Json.Serialization;
{
internal partial class JsonClassInfo
{
- private void AddPolicyProperty(Type propertyType, JsonSerializerOptions options)
+ private JsonPropertyInfo AddProperty(Type propertyType, PropertyInfo propertyInfo, Type parentClassType, JsonSerializerOptions options)
{
- // A policy property is not a real property on a type; instead it leverages the existing converter
- // logic and generic support to avoid boxing. It is used with values types and elements from collections and
- // dictionaries. Typically it would represent a CLR type such as System.String.
- PolicyProperty = AddProperty(
- propertyType,
- propertyInfo: null, // Not a real property so this is null.
- classType: typeof(object), // A dummy type (not used).
- options: options);
- }
-
- private JsonPropertyInfo AddProperty(Type propertyType, PropertyInfo propertyInfo, Type classType, JsonSerializerOptions options)
- {
- JsonPropertyInfo jsonInfo;
-
- // Get implemented type, if applicable.
- // Will return the propertyType itself if it's a non-enumerable, string, natively supported collection,
- // or if a custom converter has been provided for the type.
- Type implementedType = GetImplementedCollectionType(classType, propertyType, propertyInfo, out JsonConverter converter, options);
-
- if (implementedType != propertyType)
- {
- jsonInfo = CreateProperty(implementedType, implementedType, implementedType, propertyInfo, typeof(object), converter, options);
- }
- else
- {
- jsonInfo = CreateProperty(propertyType, propertyType, propertyType, propertyInfo, classType, converter, options);
- }
-
- // Convert non-immutable dictionary interfaces to concrete types.
- if (IsNativelySupportedCollection(propertyType) && implementedType.IsInterface && jsonInfo.ClassType == ClassType.Dictionary)
+ bool hasIgnoreAttribute = (JsonPropertyInfo.GetAttribute<JsonIgnoreAttribute>(propertyInfo) != null);
+ if (hasIgnoreAttribute)
{
- JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(jsonInfo.ElementType, options);
-
- Type newPropertyType = elementPropertyInfo.GetDictionaryConcreteType();
- if (implementedType != newPropertyType)
- {
- jsonInfo = CreateProperty(propertyType, newPropertyType, implementedType, propertyInfo, classType, converter, options);
- }
- else
- {
- jsonInfo = CreateProperty(propertyType, implementedType, implementedType, propertyInfo, classType, converter, options);
- }
+ return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(propertyInfo, options);
}
- else if (jsonInfo.ClassType == ClassType.Enumerable &&
- !implementedType.IsArray &&
- ((IsDeserializedByAssigningFromList(implementedType) && IsNativelySupportedCollection(propertyType)) || IsSetInterface(implementedType)))
- {
- JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(jsonInfo.ElementType, options);
- // Get a runtime type for the implemented property. e.g. ISet<T> -> HashSet<T>, ICollection -> List<object>
- // We use the element's JsonPropertyInfo so we can utilize the generic support.
- Type newPropertyType = elementPropertyInfo.GetConcreteType(implementedType);
- if ((implementedType != newPropertyType) && implementedType.IsAssignableFrom(newPropertyType))
- {
- jsonInfo = CreateProperty(propertyType, newPropertyType, implementedType, propertyInfo, classType, converter, options);
- }
- else
- {
- jsonInfo = CreateProperty(propertyType, implementedType, implementedType, propertyInfo, classType, converter, options);
- }
- }
- else if (propertyType != implementedType)
- {
- jsonInfo = CreateProperty(propertyType, implementedType, implementedType, propertyInfo, classType, converter, options);
- }
+ ClassType classType = GetClassType(
+ propertyType,
+ parentClassType,
+ propertyInfo,
+ out Type runtimeType,
+ out Type elementType,
+ out Type nullableUnderlyingType,
+ out _,
+ out JsonConverter converter,
+ checkForAddMethod: false,
+ options);
- return jsonInfo;
+ return CreateProperty(
+ declaredPropertyType: propertyType,
+ runtimePropertyType: runtimeType,
+ propertyInfo,
+ parentClassType,
+ collectionElementType: elementType,
+ nullableUnderlyingType,
+ converter,
+ classType,
+ options);
}
internal static JsonPropertyInfo CreateProperty(
Type declaredPropertyType,
Type runtimePropertyType,
- Type implementedPropertyType,
PropertyInfo propertyInfo,
Type parentClassType,
+ Type collectionElementType,
+ Type nullableUnderlyingType,
JsonConverter converter,
+ ClassType classType,
JsonSerializerOptions options)
{
- bool hasIgnoreAttribute = (JsonPropertyInfo.GetAttribute<JsonIgnoreAttribute>(propertyInfo) != null);
- if (hasIgnoreAttribute)
- {
- return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(propertyInfo, options);
- }
-
- // Obtain the custom converter for the property.
- if (converter == null)
- {
- converter = options.DetermineConverterForProperty(parentClassType, runtimePropertyType, propertyInfo);
- }
+ bool treatAsNullable = nullableUnderlyingType != null;
// Obtain the type of the JsonPropertyInfo class to construct.
Type propertyInfoClassType;
- if (runtimePropertyType.IsGenericType && runtimePropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
+
+ if (treatAsNullable && converter != null)
{
- if (converter != null)
- {
- propertyInfoClassType = typeof(JsonPropertyInfoNotNullable<,,,>).MakeGenericType(
- parentClassType,
- declaredPropertyType,
- runtimePropertyType,
- runtimePropertyType);
- }
- else
- {
- // Attempt to find converter for underlying type.
- Type typeToConvert = Nullable.GetUnderlyingType(runtimePropertyType);
- converter = options.DetermineConverterForProperty(parentClassType, typeToConvert, propertyInfo);
- propertyInfoClassType = typeof(JsonPropertyInfoNullable<,>).MakeGenericType(parentClassType, typeToConvert);
- }
+ propertyInfoClassType = typeof(JsonPropertyInfoNullable<,>).MakeGenericType(parentClassType, nullableUnderlyingType);
}
else
{
Type typeToConvert = converter?.TypeToConvert;
if (typeToConvert == null)
{
- if (IsNativelySupportedCollection(declaredPropertyType))
- {
- typeToConvert = implementedPropertyType;
- }
- else
- {
- typeToConvert = declaredPropertyType;
- }
+ typeToConvert = declaredPropertyType;
}
// For the covariant case, create JsonPropertyInfoNotNullable. The generic constraints are "where TConverter : TDeclaredProperty".
args: null,
culture: null);
- // Obtain the collection element type.
- Type collectionElementType = null;
- if (converter == null)
- {
- switch (GetClassType(runtimePropertyType, options))
- {
- case ClassType.Enumerable:
- case ClassType.Dictionary:
- case ClassType.IDictionaryConstructible:
- case ClassType.Unknown:
- collectionElementType = GetElementType(runtimePropertyType, parentClassType, propertyInfo, options);
- break;
- }
- }
-
- // Initialize the JsonPropertyInfo.
jsonPropertyInfo.Initialize(
parentClassType,
declaredPropertyType,
runtimePropertyType,
- implementedPropertyType,
+ runtimeClassType: classType,
propertyInfo,
collectionElementType,
converter,
+ treatAsNullable,
options);
return jsonPropertyInfo;
internal JsonPropertyInfo CreateRootObject(JsonSerializerOptions options)
{
+ JsonConverter converter = options.DetermineConverterForProperty(Type, Type, propertyInfo: null);
+
return CreateProperty(
declaredPropertyType: Type,
runtimePropertyType: Type,
- implementedPropertyType: Type,
propertyInfo: null,
parentClassType: Type,
- converter: null,
- options: options);
+ ElementType,
+ Nullable.GetUnderlyingType(Type),
+ converter,
+ ClassType,
+ options);
}
internal JsonPropertyInfo CreatePolymorphicProperty(JsonPropertyInfo property, Type runtimePropertyType, JsonSerializerOptions options)
{
+ ClassType classType = GetClassType(
+ runtimePropertyType,
+ Type,
+ property.PropertyInfo,
+ out _,
+ out Type elementType,
+ out Type nullableType,
+ out _,
+ out JsonConverter converter,
+ checkForAddMethod: false,
+ options);
+
JsonPropertyInfo runtimeProperty = CreateProperty(
- property.DeclaredPropertyType, runtimePropertyType,
- property.ImplementedPropertyType,
+ property.DeclaredPropertyType,
+ runtimePropertyType,
property.PropertyInfo,
parentClassType: Type,
- converter: null,
+ collectionElementType: elementType,
+ nullableType,
+ converter,
+ classType,
options: options);
property.CopyRuntimeSettingsTo(runtimeProperty);
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Collections;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Reflection;
-using System.Text.Json.Serialization;
-using System.Text.Json.Serialization.Converters;
-
-namespace System.Text.Json
-{
- internal partial class JsonClassInfo
- {
- public const string ImmutableNamespaceName = "System.Collections.Immutable";
-
- private const string EnumerableGenericInterfaceTypeName = "System.Collections.Generic.IEnumerable`1";
- private const string EnumerableInterfaceTypeName = "System.Collections.IEnumerable";
-
- private const string ListInterfaceTypeName = "System.Collections.IList";
- private const string ListGenericInterfaceTypeName = "System.Collections.Generic.IList`1";
- private const string ListGenericTypeName = "System.Collections.Generic.List`1";
-
- private const string CollectionGenericInterfaceTypeName = "System.Collections.Generic.ICollection`1";
- private const string CollectionInterfaceTypeName = "System.Collections.ICollection";
-
- private const string ReadOnlyListGenericInterfaceTypeName = "System.Collections.Generic.IReadOnlyList`1";
-
- private const string ReadOnlyCollectionGenericInterfaceTypeName = "System.Collections.Generic.IReadOnlyCollection`1";
-
- public const string HashtableTypeName = "System.Collections.Hashtable";
- public const string SortedListTypeName = "System.Collections.SortedList";
-
- public const string StackTypeName = "System.Collections.Stack";
- public const string StackGenericTypeName = "System.Collections.Generic.Stack`1";
-
- public const string QueueTypeName = "System.Collections.Queue";
- public const string QueueGenericTypeName = "System.Collections.Generic.Queue`1";
-
- public const string SetGenericInterfaceTypeName = "System.Collections.Generic.ISet`1";
- public const string SortedSetGenericTypeName = "System.Collections.Generic.SortedSet`1";
- public const string HashSetGenericTypeName = "System.Collections.Generic.HashSet`1";
-
- public const string LinkedListGenericTypeName = "System.Collections.Generic.LinkedList`1";
-
- public const string DictionaryInterfaceTypeName = "System.Collections.IDictionary";
- public const string DictionaryGenericTypeName = "System.Collections.Generic.Dictionary`2";
- public const string DictionaryGenericInterfaceTypeName = "System.Collections.Generic.IDictionary`2";
- public const string ReadOnlyDictionaryGenericInterfaceTypeName = "System.Collections.Generic.IReadOnlyDictionary`2";
- public const string SortedDictionaryGenericTypeName = "System.Collections.Generic.SortedDictionary`2";
- public const string KeyValuePairGenericTypeName = "System.Collections.Generic.KeyValuePair`2";
-
- public const string ArrayListTypeName = "System.Collections.ArrayList";
-
- // In the order we wish to detect a derived type.
- private static readonly Type[] s_genericInterfacesWithAddMethods = new Type[]
- {
- typeof(IDictionary<,>),
- typeof(ICollection<>),
- };
-
- // In the order we wish to detect a derived type.
- private static readonly Type[] s_nonGenericInterfacesWithAddMethods = new Type[]
- {
- typeof(IDictionary),
- typeof(IList),
- };
-
- // In the order we wish to detect a derived type.
- private static readonly Type[] s_genericInterfacesWithoutAddMethods = new Type[]
- {
- typeof(IReadOnlyDictionary<,>),
- typeof(IReadOnlyCollection<>),
- typeof(IReadOnlyList<>),
- typeof(IEnumerable<>),
- };
-
- // Any additional natively supported generic collection must be registered here.
- private static readonly HashSet<string> s_nativelySupportedGenericCollections = new HashSet<string>()
- {
- ListGenericTypeName,
- EnumerableGenericInterfaceTypeName,
- ListGenericInterfaceTypeName,
- CollectionGenericInterfaceTypeName,
- ReadOnlyListGenericInterfaceTypeName,
- ReadOnlyCollectionGenericInterfaceTypeName,
- SetGenericInterfaceTypeName,
- StackGenericTypeName,
- QueueGenericTypeName,
- HashSetGenericTypeName,
- LinkedListGenericTypeName,
- SortedSetGenericTypeName,
- DictionaryInterfaceTypeName,
- DictionaryGenericTypeName,
- DictionaryGenericInterfaceTypeName,
- ReadOnlyDictionaryGenericInterfaceTypeName,
- SortedDictionaryGenericTypeName,
- KeyValuePairGenericTypeName,
- DefaultImmutableEnumerableConverter.ImmutableArrayGenericTypeName,
- DefaultImmutableEnumerableConverter.ImmutableListGenericTypeName,
- DefaultImmutableEnumerableConverter.ImmutableListGenericInterfaceTypeName,
- DefaultImmutableEnumerableConverter.ImmutableStackGenericTypeName,
- DefaultImmutableEnumerableConverter.ImmutableStackGenericInterfaceTypeName,
- DefaultImmutableEnumerableConverter.ImmutableQueueGenericTypeName,
- DefaultImmutableEnumerableConverter.ImmutableQueueGenericInterfaceTypeName,
- DefaultImmutableEnumerableConverter.ImmutableSortedSetTypeName,
- DefaultImmutableEnumerableConverter.ImmutableSortedSetGenericTypeName,
- DefaultImmutableEnumerableConverter.ImmutableHashSetGenericTypeName,
- DefaultImmutableEnumerableConverter.ImmutableSetGenericInterfaceTypeName,
- DefaultImmutableDictionaryConverter.ImmutableDictionaryGenericTypeName,
- DefaultImmutableDictionaryConverter.ImmutableDictionaryGenericInterfaceTypeName,
- DefaultImmutableDictionaryConverter.ImmutableSortedDictionaryGenericTypeName,
- };
-
- // Any additional natively supported non-generic collection must be registered here.
- private static readonly HashSet<string> s_nativelySupportedNonGenericCollections = new HashSet<string>()
- {
- EnumerableInterfaceTypeName,
- CollectionInterfaceTypeName,
- ListInterfaceTypeName,
- DictionaryInterfaceTypeName,
- StackTypeName,
- QueueTypeName,
- HashtableTypeName,
- ArrayListTypeName,
- SortedListTypeName,
- };
-
- public static Type GetImplementedCollectionType(
- Type parentClassType,
- Type queryType,
- PropertyInfo propertyInfo,
- out JsonConverter converter,
- JsonSerializerOptions options)
- {
- Debug.Assert(queryType != null);
-
- if (!(typeof(IEnumerable).IsAssignableFrom(queryType)) ||
- queryType == typeof(string) ||
- queryType.IsInterface ||
- queryType.IsArray ||
- IsNativelySupportedCollection(queryType))
- {
- converter = null;
- return queryType;
- }
-
- // If a converter was provided, we should not detect implemented types and instead use the converter later.
- converter = options.DetermineConverterForProperty(parentClassType, queryType, propertyInfo);
- if (converter != null)
- {
- return queryType;
- }
-
- Type baseType = queryType.GetTypeInfo().BaseType;
-
- // Check if the base type is a supported concrete collection.
- if (IsNativelySupportedCollection(baseType))
- {
- return baseType;
- }
-
- // Try generic interfaces with add methods.
- foreach (Type candidate in s_genericInterfacesWithAddMethods)
- {
- Type derivedGeneric = ExtractGenericInterface(queryType, candidate);
- if (derivedGeneric != null)
- {
- return derivedGeneric;
- }
- }
-
- // Try non-generic interfaces with add methods.
- foreach (Type candidate in s_nonGenericInterfacesWithAddMethods)
- {
- if (candidate.IsAssignableFrom(queryType))
- {
- return candidate;
- }
- }
-
- // Try generic interfaces without add methods
- foreach (Type candidate in s_genericInterfacesWithoutAddMethods)
- {
- Type derivedGeneric = ExtractGenericInterface(queryType, candidate);
- if (derivedGeneric != null)
- {
- return derivedGeneric;
- }
- }
-
- return typeof(IEnumerable);
- }
-
- public static bool IsDeserializedByAssigningFromList(Type type)
- {
- if (type.IsGenericType)
- {
- switch (type.GetGenericTypeDefinition().FullName)
- {
- case EnumerableGenericInterfaceTypeName:
- case ListGenericInterfaceTypeName:
- case CollectionGenericInterfaceTypeName:
- case ReadOnlyListGenericInterfaceTypeName:
- case ReadOnlyCollectionGenericInterfaceTypeName:
- return true;
- default:
- return false;
- }
- }
- else
- {
- switch (type.FullName)
- {
- case EnumerableInterfaceTypeName:
- case ListInterfaceTypeName:
- case CollectionInterfaceTypeName:
- return true;
- default:
- return false;
- }
- }
- }
-
- public static bool IsSetInterface(Type type)
- {
- return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ISet<>);
- }
-
- public static bool HasConstructorThatTakesGenericIEnumerable(Type type, JsonSerializerOptions options)
- {
- Type elementType = GetElementType(type, parentType: null, memberInfo: null, options);
- return type.GetConstructor(new Type[] { typeof(List<>).MakeGenericType(elementType) }) != null;
- }
-
- public static bool IsDeserializedByConstructingWithIList(Type type)
- {
- switch (type.FullName)
- {
- case StackTypeName:
- case QueueTypeName:
- case ArrayListTypeName:
- return true;
- default:
- return false;
- }
- }
-
- public static bool IsDeserializedByConstructingWithIDictionary(Type type)
- {
- switch (type.FullName)
- {
- case HashtableTypeName:
- case SortedListTypeName:
- return true;
- default:
- return false;
- }
- }
-
- public static bool IsNativelySupportedCollection(Type queryType)
- {
- Debug.Assert(queryType != null);
-
- if (queryType.IsGenericType)
- {
- return s_nativelySupportedGenericCollections.Contains(queryType.GetGenericTypeDefinition().FullName);
- }
-
- return s_nativelySupportedNonGenericCollections.Contains(queryType.FullName);
- }
-
- // The following methods were copied verbatim from AspNetCore:
- // https://github.com/aspnet/AspNetCore/blob/13ae0057fbb11fd84fcee8fca46ebc1b2d7c1e6a/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs.
-
- /// <summary>
- /// Determine whether <paramref name="queryType"/> is or implements a closed generic <see cref="Type"/>
- /// created from <paramref name="interfaceType"/>.
- /// </summary>
- /// <param name="queryType">The <see cref="Type"/> of interest.</param>
- /// <param name="interfaceType">The open generic <see cref="Type"/> to match. Usually an interface.</param>
- /// <returns>
- /// The closed generic <see cref="Type"/> created from <paramref name="interfaceType"/> that
- /// <paramref name="queryType"/> is or implements. <c>null</c> if the two <see cref="Type"/>s have no such
- /// relationship.
- /// </returns>
- /// <remarks>
- /// This method will return <paramref name="queryType"/> if <paramref name="interfaceType"/> is
- /// <c>typeof(KeyValuePair{,})</c>, and <paramref name="queryType"/> is
- /// <c>typeof(KeyValuePair{string, object})</c>.
- /// </remarks>
- public static Type ExtractGenericInterface(Type queryType, Type interfaceType)
- {
- if (queryType == null)
- {
- throw new ArgumentNullException(nameof(queryType));
- }
-
- if (interfaceType == null)
- {
- throw new ArgumentNullException(nameof(interfaceType));
- }
-
- if (IsGenericInstantiation(queryType, interfaceType))
- {
- // queryType matches (i.e. is a closed generic type created from) the open generic type.
- return queryType;
- }
-
- // Otherwise check all interfaces the type implements for a match.
- // - If multiple different generic instantiations exists, we want the most derived one.
- // - If that doesn't break the tie, then we sort alphabetically so that it's deterministic.
- //
- // We do this by looking at interfaces on the type, and recursing to the base type
- // if we don't find any matches.
- return GetGenericInstantiation(queryType, interfaceType);
- }
-
- private static bool IsGenericInstantiation(Type candidate, Type interfaceType)
- {
- return
- candidate.GetTypeInfo().IsGenericType &&
- candidate.GetGenericTypeDefinition() == interfaceType;
- }
-
- private static Type GetGenericInstantiation(Type queryType, Type interfaceType)
- {
- Type bestMatch = null;
- Type[] interfaces = queryType.GetInterfaces();
- foreach (Type @interface in interfaces)
- {
- if (IsGenericInstantiation(@interface, interfaceType))
- {
- if (bestMatch == null)
- {
- bestMatch = @interface;
- }
- else if (StringComparer.Ordinal.Compare(@interface.FullName, bestMatch.FullName) < 0)
- {
- bestMatch = @interface;
- }
- else
- {
- // There are two matches at this level of the class hierarchy, but @interface is after
- // bestMatch in the sort order.
- }
- }
- }
-
- if (bestMatch != null)
- {
- return bestMatch;
- }
-
- // BaseType will be null for object and interfaces, which means we've reached 'bottom'.
- Type baseType = queryType?.GetTypeInfo().BaseType;
- if (baseType == null)
- {
- return null;
- }
- else
- {
- return GetGenericInstantiation(baseType, interfaceType);
- }
- }
- }
-}
public delegate object ConstructorDelegate();
public ConstructorDelegate CreateObject { get; private set; }
- public ConstructorDelegate CreateConcreteDictionary { get; private set; }
public ClassType ClassType { get; private set; }
if (_elementClassInfo == null && ElementType != null)
{
Debug.Assert(ClassType == ClassType.Enumerable ||
- ClassType == ClassType.Dictionary ||
- ClassType == ClassType.IDictionaryConstructible);
+ ClassType == ClassType.Dictionary);
_elementClassInfo = Options.GetOrAddClass(ElementType);
}
{
Type = type;
Options = options;
- ClassType = GetClassType(type, options);
- CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);
+ ClassType = GetClassType(
+ type,
+ parentClassType: type,
+ propertyInfo: null,
+ out Type runtimeType,
+ out Type elementType,
+ out Type nullableUnderlyingType,
+ out MethodInfo addMethod,
+ out JsonConverter converter,
+ checkForAddMethod: true,
+ options);
// Ignore properties on enumerable.
switch (ClassType)
{
case ClassType.Object:
{
+ CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);
+
PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
Dictionary<string, JsonPropertyInfo> cache = CreatePropertyCache(properties.Length);
case ClassType.Dictionary:
{
// Add a single property that maps to the class type so we can have policies applied.
- AddPolicyProperty(type, options);
-
- Type objectType;
- if (IsNativelySupportedCollection(type))
- {
- // Use the type from the property policy to get any late-bound concrete types (from an interface like IDictionary).
- objectType = PolicyProperty.RuntimePropertyType;
- }
- else
- {
- // We need to create the declared instance for types implementing natively supported collections.
- objectType = PolicyProperty.DeclaredPropertyType;
- }
-
- CreateObject = options.MemberAccessorStrategy.CreateConstructor(objectType);
-
- ElementType = GetElementType(type, parentType: null, memberInfo: null, options: options);
+ ElementType = elementType;
+ AddItemToObject = addMethod;
+
+ // A policy property is not a real property on a type; instead it leverages the existing converter
+ // logic and generic support to avoid boxing. It is used with values types, elements from collections and
+ // dictionaries, and collections themselves. Typically it would represent a CLR type such as System.String.
+ PolicyProperty = CreateProperty(
+ declaredPropertyType: type,
+ runtimePropertyType: runtimeType,
+ propertyInfo: null, // Not a real property so this is null.
+ parentClassType: typeof(object),
+ collectionElementType: elementType,
+ nullableUnderlyingType,
+ converter: null,
+ ClassType,
+ options);
+
+ CreateObject = options.MemberAccessorStrategy.CreateConstructor(PolicyProperty.RuntimePropertyType);
}
break;
- case ClassType.IDictionaryConstructible:
+ case ClassType.Value:
{
- // Add a single property that maps to the class type so we can have policies applied.
- AddPolicyProperty(type, options);
-
- ElementType = GetElementType(type, parentType: null, memberInfo: null, options: options);
+ CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);
- CreateConcreteDictionary = options.MemberAccessorStrategy.CreateConstructor(
- typeof(Dictionary<,>).MakeGenericType(typeof(string), ElementType));
-
- CreateObject = options.MemberAccessorStrategy.CreateConstructor(PolicyProperty.DeclaredPropertyType);
+ // Add a single property that maps to the class type so we can have policies applied.
+ //AddPolicyPropertyForValue(type, options);
+ PolicyProperty = CreateProperty(
+ declaredPropertyType: type,
+ runtimePropertyType: runtimeType,
+ propertyInfo: null, // Not a real property so this is null.
+ parentClassType: typeof(object),
+ collectionElementType: null,
+ nullableUnderlyingType,
+ converter,
+ ClassType,
+ options);
}
break;
- case ClassType.Value:
- // Add a single property that maps to the class type so we can have policies applied.
- AddPolicyProperty(type, options);
- break;
case ClassType.Unknown:
- // Add a single property that maps to the class type so we can have policies applied.
- AddPolicyProperty(type, options);
- PropertyCache = new Dictionary<string, JsonPropertyInfo>();
- PropertyCacheArray = Array.Empty<JsonPropertyInfo>();
+ {
+ CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);
+
+ // Add a single property that maps to the class type so we can have policies applied.
+ //AddPolicyPropertyForValue(type, options);
+ PolicyProperty = CreateProperty(
+ declaredPropertyType: type,
+ runtimePropertyType: runtimeType,
+ propertyInfo: null, // Not a real property so this is null.
+ parentClassType: typeof(object),
+ collectionElementType: null,
+ nullableUnderlyingType,
+ converter,
+ ClassType,
+ options);
+
+ PropertyCache = new Dictionary<string, JsonPropertyInfo>();
+ PropertyCacheArray = Array.Empty<JsonPropertyInfo>();
+ }
break;
default:
Debug.Fail($"Unexpected class type: {ClassType}");
public JsonPropertyInfo PolicyProperty { get; private set; }
+ public MethodInfo AddItemToObject { get; private set; }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryIsPropertyRefEqual(in PropertyRef propertyRef, ReadOnlySpan<byte> propertyName, ulong key, ref JsonPropertyInfo info)
{
return key;
}
- // Return the element type of the IEnumerable or return null if not an IEnumerable.
- public static Type GetElementType(Type propertyType, Type parentType, MemberInfo memberInfo, JsonSerializerOptions options)
+ // This method gets the runtime information for a given type or property.
+ // The runtime information consists of the following:
+ // - class type,
+ // - runtime type,
+ // - element type (if the type is a collection),
+ // - the underlying type (if the type is nullable type e.g. int?),
+ // - the "add" method (if the type is a non-dictionary collection which doesn't implement IList
+ // e.g. typeof(Stack<int>), where we retrieve the void Push(string) method), and
+ // - the converter (either native or custom), if one exists.
+ public static ClassType GetClassType(
+ Type type,
+ Type parentClassType,
+ PropertyInfo propertyInfo,
+ out Type runtimeType,
+ out Type elementType,
+ out Type nullableUnderlyingType,
+ out MethodInfo addMethod,
+ out JsonConverter converter,
+ bool checkForAddMethod,
+ JsonSerializerOptions options)
{
- // We want to handle as the implemented collection type, if applicable.
- Type implementedType = GetImplementedCollectionType(parentType, propertyType, propertyInfo: null, out _, options);
+ Debug.Assert(type != null);
- if (!typeof(IEnumerable).IsAssignableFrom(implementedType))
- {
- return null;
- }
+ runtimeType = type;
- // Check for Array.
- Type elementType = implementedType.GetElementType();
- if (elementType != null)
- {
- return elementType;
- }
+ nullableUnderlyingType = Nullable.GetUnderlyingType(type);
- // Check for Dictionary<TKey, TValue> or IEnumerable<T>
- if (implementedType.IsGenericType)
+ // Type is nullable e.g. typeof(int?).
+ if (nullableUnderlyingType != null)
{
- Type[] args = implementedType.GetGenericArguments();
- ClassType classType = GetClassType(implementedType, options);
+ // Check if there's a converter for this nullable type, e.g. do we have a converter that implements
+ // JsonConverter<int?> if the type is typeof(int?)?
+ converter = options.DetermineConverterForProperty(parentClassType, type, propertyInfo);
- if ((classType == ClassType.Dictionary || classType == ClassType.IDictionaryConstructible) &&
- args.Length >= 2 && // It is >= 2 in case there is a IDictionary<TKey, TValue, TSomeExtension>.
- args[0].UnderlyingSystemType == typeof(string))
+ if (converter == null)
{
- return args[1];
+ // No converter. We'll check below if there's a converter for the non-nullable type e.g.
+ // one that implements JsonConverter<int>, given the type is typeof(int?).
+ type = nullableUnderlyingType;
}
-
- if (classType == ClassType.Enumerable && args.Length >= 1) // It is >= 1 in case there is an IEnumerable<T, TSomeExtension>.
+ else
{
- return args[0];
+ elementType = default;
+ addMethod = default;
+ // Don't treat the type as a Nullable when creating the property info later on, since we have a converter for it.
+ nullableUnderlyingType = default;
+ return ClassType.Value;
}
}
- if (implementedType.IsAssignableFrom(typeof(IList)) ||
- implementedType.IsAssignableFrom(typeof(IDictionary)) ||
- IsDeserializedByConstructingWithIList(implementedType) ||
- IsDeserializedByConstructingWithIDictionary(implementedType))
+ converter = options.DetermineConverterForProperty(parentClassType, type, propertyInfo);
+
+ if (converter != null)
{
- return typeof(object);
+ elementType = default;
+ addMethod = default;
+ return type == typeof(object) ? ClassType.Unknown : ClassType.Value;
}
- throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(propertyType, parentType, memberInfo);
- }
-
- public static ClassType GetClassType(Type type, JsonSerializerOptions options)
- {
- Debug.Assert(type != null);
-
- // We want to handle as the implemented collection type, if applicable.
- Type implementedType = GetImplementedCollectionType(typeof(object), type, propertyInfo: null, out _, options);
+ runtimeType = type;
- if (implementedType.IsGenericType && implementedType.GetGenericTypeDefinition() == typeof(Nullable<>))
+ if (!(typeof(IEnumerable)).IsAssignableFrom(type))
{
- implementedType = Nullable.GetUnderlyingType(implementedType);
+ elementType = null;
+ addMethod = default;
+ return ClassType.Object;
}
- if (implementedType == typeof(object))
+ if (type.IsArray)
{
- return ClassType.Unknown;
+ elementType = type.GetElementType();
+ addMethod = default;
+ return ClassType.Enumerable;
}
- if (options.HasConverter(implementedType))
+ if (type.FullName.StartsWith("System.Collections.Generic.IEnumerable`1"))
{
- return ClassType.Value;
+ elementType = type.GetGenericArguments()[0];
+ runtimeType = typeof(List<>).MakeGenericType(elementType);
+ addMethod = default;
+ return ClassType.Enumerable;
+ }
+ else if (type.FullName.StartsWith("System.Collections.Generic.IDictionary`2") ||
+ type.FullName.StartsWith("System.Collections.Generic.IReadOnlyDictionary`2"))
+ {
+ Type[] genericTypes = type.GetGenericArguments();
+
+ elementType = genericTypes[1];
+ runtimeType = typeof(Dictionary<,>).MakeGenericType(genericTypes[0], elementType);
+ addMethod = default;
+ return ClassType.Dictionary;
}
- if (DefaultImmutableDictionaryConverter.IsImmutableDictionary(implementedType) ||
- IsDeserializedByConstructingWithIDictionary(implementedType))
{
- return ClassType.IDictionaryConstructible;
+ Type genericIDictionaryType = type.GetInterface("System.Collections.Generic.IDictionary`2") ?? type.GetInterface("System.Collections.Generic.IReadOnlyDictionary`2");
+ if (genericIDictionaryType != null)
+ {
+ Type[] genericTypes = genericIDictionaryType.GetGenericArguments();
+ elementType = genericTypes[1];
+ addMethod = default;
+
+ if (type.IsInterface)
+ {
+ Type concreteDictionaryType = typeof(Dictionary<,>).MakeGenericType(genericTypes[0], genericTypes[1]);
+
+ if (type.IsAssignableFrom(concreteDictionaryType))
+ {
+ runtimeType = concreteDictionaryType;
+ }
+ }
+
+ return ClassType.Dictionary;
+ }
}
- if (typeof(IDictionary).IsAssignableFrom(implementedType) || IsDictionaryClassType(implementedType))
+ if (typeof(IDictionary).IsAssignableFrom(type))
{
- // Special case for immutable dictionaries
- if (type != implementedType && !IsNativelySupportedCollection(type))
+ elementType = typeof(object);
+ addMethod = default;
+
+ if (type.IsInterface)
{
- return ClassType.IDictionaryConstructible;
+ Type concreteDictionaryType = typeof(Dictionary<string, object>);
+
+ if (type.IsAssignableFrom(concreteDictionaryType))
+ {
+ runtimeType = concreteDictionaryType;
+ }
}
return ClassType.Dictionary;
}
- if (typeof(IEnumerable).IsAssignableFrom(implementedType))
{
- return ClassType.Enumerable;
+ Type genericIEnumerableType = type.GetInterface("System.Collections.Generic.IEnumerable`1");
+
+ if (genericIEnumerableType != null)
+ {
+ elementType = genericIEnumerableType.GetGenericArguments()[0];
+ }
+ else
+ {
+ elementType = typeof(object);
+ }
}
- return ClassType.Object;
- }
+ if (typeof(IList).IsAssignableFrom(type))
+ {
+ addMethod = default;
- public static bool IsDictionaryClassType(Type type)
- {
- return (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IDictionary<,>) ||
- type.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)));
+ if (type.IsInterface)
+ {
+ Type concreteListType = typeof(List<>).MakeGenericType(elementType);
+ if (type.IsAssignableFrom(concreteListType))
+ {
+ runtimeType = concreteListType;
+ }
+ }
+ }
+ else if (type.IsInterface)
+ {
+ addMethod = default;
+
+ Type concreteType = typeof(List<>).MakeGenericType(elementType);
+ if (type.IsAssignableFrom(concreteType))
+ {
+ runtimeType = concreteType;
+ }
+ else
+ {
+ concreteType = typeof(HashSet<>).MakeGenericType(elementType);
+ if (type.IsAssignableFrom(concreteType))
+ {
+ runtimeType = concreteType;
+ }
+ }
+ }
+ else
+ {
+ addMethod = default;
+
+ if (checkForAddMethod)
+ {
+ Type genericICollectionType = type.GetInterface("System.Collections.Generic.ICollection`1");
+ if (genericICollectionType != null)
+ {
+ addMethod = genericICollectionType.GetMethod("Add");
+ }
+ else
+ {
+ // Non-immutable stack or queue.
+ MethodInfo methodInfo = type.GetMethod("Push") ?? type.GetMethod("Enqueue");
+ if (methodInfo?.ReturnType == typeof(void))
+ {
+ addMethod = methodInfo;
+ }
+ }
+ }
+ }
+
+ return ClassType.Enumerable;
}
}
}
internal abstract class JsonPropertyInfo
{
// Cache the converters so they don't get created for every enumerable property.
- private static readonly JsonEnumerableConverter s_jsonDerivedEnumerableConverter = new DefaultDerivedEnumerableConverter();
private static readonly JsonEnumerableConverter s_jsonArrayConverter = new DefaultArrayConverter();
- private static readonly JsonEnumerableConverter s_jsonICollectionConverter = new DefaultICollectionConverter();
private static readonly JsonEnumerableConverter s_jsonImmutableEnumerableConverter = new DefaultImmutableEnumerableConverter();
- private static readonly JsonDictionaryConverter s_jsonDerivedDictionaryConverter = new DefaultDerivedDictionaryConverter();
- private static readonly JsonDictionaryConverter s_jsonIDictionaryConverter = new DefaultIDictionaryConverter();
private static readonly JsonDictionaryConverter s_jsonImmutableDictionaryConverter = new DefaultImmutableDictionaryConverter();
public static readonly JsonPropertyInfo s_missingProperty = GetMissingProperty();
private JsonClassInfo _declaredTypeClassInfo;
public bool CanBeNull { get; private set; }
+ public bool IsImmutableArray { get; private set; }
public ClassType ClassType;
public abstract IList CreateConverterList();
- public abstract IEnumerable CreateDerivedEnumerableInstance(ref ReadStack state, JsonPropertyInfo collectionPropertyInfo, IList sourceList);
-
- public abstract object CreateDerivedDictionaryInstance(ref ReadStack state, JsonPropertyInfo collectionPropertyInfo, IDictionary sourceDictionary);
-
- public abstract IEnumerable CreateIEnumerableInstance(ref ReadStack state, Type parentType, IList sourceList);
-
- public abstract IDictionary CreateIDictionaryInstance(ref ReadStack state, Type parentType, IDictionary sourceDictionary);
+ public abstract IDictionary CreateConverterDictionary();
public abstract IEnumerable CreateImmutableCollectionInstance(ref ReadStack state, Type collectionType, string delegateKey, IList sourceList, JsonSerializerOptions options);
public Type DeclaredPropertyType { get; private set; }
- public Type ImplementedPropertyType { get; private set; }
-
private void DeterminePropertyName()
{
if (PropertyInfo == null)
private void DetermineSerializationCapabilities()
{
- if (ClassType != ClassType.Enumerable &&
- ClassType != ClassType.Dictionary &&
- ClassType != ClassType.IDictionaryConstructible)
+ if ((ClassType & (ClassType.Enumerable | ClassType.Dictionary)) == 0)
{
// We serialize if there is a getter + not ignoring readonly properties.
ShouldSerialize = HasGetter && (HasSetter || !Options.IgnoreReadOnlyProperties);
EnumerableConverter = s_jsonArrayConverter;
}
- else if (ClassType == ClassType.IDictionaryConstructible)
+ else if (ClassType == ClassType.Dictionary && DefaultImmutableDictionaryConverter.IsImmutableDictionary(RuntimePropertyType))
{
- // Natively supported type.
- if (DeclaredPropertyType == ImplementedPropertyType)
- {
- if (RuntimePropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName))
- {
- DefaultImmutableDictionaryConverter.RegisterImmutableDictionary(
- RuntimePropertyType, JsonClassInfo.GetElementType(RuntimePropertyType, ParentClassType, PropertyInfo, Options), Options);
-
- DictionaryConverter = s_jsonImmutableDictionaryConverter;
- }
- else if (JsonClassInfo.IsDeserializedByConstructingWithIDictionary(RuntimePropertyType))
- {
- DictionaryConverter = s_jsonIDictionaryConverter;
- }
- }
- // Type that implements a type with ClassType IDictionaryConstructible.
- else
- {
- DictionaryConverter = s_jsonDerivedDictionaryConverter;
- }
+ DefaultImmutableDictionaryConverter.RegisterImmutableDictionary(RuntimePropertyType, ElementType, Options);
+ DictionaryConverter = s_jsonImmutableDictionaryConverter;
}
- else if (ClassType == ClassType.Enumerable)
+ else if (ClassType == ClassType.Enumerable && DefaultImmutableEnumerableConverter.IsImmutableEnumerable(RuntimePropertyType, out bool isImmutableArray))
{
- // Else if it's an implementing type whose runtime type is not assignable to IList.
- if (DeclaredPropertyType != ImplementedPropertyType &&
- (!typeof(IList).IsAssignableFrom(RuntimePropertyType) ||
- ImplementedPropertyType == typeof(ArrayList) ||
- ImplementedPropertyType == typeof(IList)))
- {
- EnumerableConverter = s_jsonDerivedEnumerableConverter;
- }
- else if (JsonClassInfo.IsDeserializedByConstructingWithIList(RuntimePropertyType) ||
- (!typeof(IList).IsAssignableFrom(RuntimePropertyType) &&
- JsonClassInfo.HasConstructorThatTakesGenericIEnumerable(RuntimePropertyType, Options)))
- {
- EnumerableConverter = s_jsonICollectionConverter;
- }
- else if (RuntimePropertyType.IsGenericType &&
- RuntimePropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName) &&
- RuntimePropertyType.GetGenericArguments().Length == 1)
- {
- DefaultImmutableEnumerableConverter.RegisterImmutableCollection(RuntimePropertyType,
- JsonClassInfo.GetElementType(RuntimePropertyType, ParentClassType, PropertyInfo, Options), Options);
- EnumerableConverter = s_jsonImmutableEnumerableConverter;
- }
+ DefaultImmutableEnumerableConverter.RegisterImmutableCollection(RuntimePropertyType, ElementType, Options);
+ EnumerableConverter = s_jsonImmutableEnumerableConverter;
+ IsImmutableArray = isImmutableArray;
}
}
}
if (_elementClassInfo == null && ElementType != null)
{
Debug.Assert(ClassType == ClassType.Enumerable ||
- ClassType == ClassType.Dictionary ||
- ClassType == ClassType.IDictionaryConstructible);
+ ClassType == ClassType.Dictionary);
_elementClassInfo = Options.GetOrAddClass(ElementType);
}
public abstract Type GetDictionaryConcreteType();
- public abstract Type GetConcreteType(Type type);
-
public virtual void GetPolicies()
{
DetermineSerializationCapabilities();
Type parentClassType,
Type declaredPropertyType,
Type runtimePropertyType,
- Type implementedPropertyType,
+ ClassType runtimeClassType,
PropertyInfo propertyInfo,
Type elementType,
JsonConverter converter,
+ bool treatAsNullable,
JsonSerializerOptions options)
{
ParentClassType = parentClassType;
DeclaredPropertyType = declaredPropertyType;
RuntimePropertyType = runtimePropertyType;
- ImplementedPropertyType = implementedPropertyType;
+ ClassType = runtimeClassType;
PropertyInfo = propertyInfo;
ElementType = elementType;
Options = options;
- IsNullableType = runtimePropertyType.IsGenericType && runtimePropertyType.GetGenericTypeDefinition() == typeof(Nullable<>);
- CanBeNull = IsNullableType || !runtimePropertyType.IsValueType;
+ CanBeNull = treatAsNullable || !runtimePropertyType.IsValueType;
if (converter != null)
{
ConverterBase = converter;
HasInternalConverter = (converter.GetType().Assembly == GetType().Assembly);
-
- // Avoid calling GetClassType since it will re-ask if there is a converter which is slow.
- if (runtimePropertyType == typeof(object))
- {
- ClassType = ClassType.Unknown;
- }
- else
- {
- ClassType = ClassType.Value;
- }
- }
- // Special case for immutable collections.
- else if (declaredPropertyType != implementedPropertyType && !JsonClassInfo.IsNativelySupportedCollection(declaredPropertyType))
- {
- ClassType = JsonClassInfo.GetClassType(declaredPropertyType, options);
- }
- else
- {
- ClassType = JsonClassInfo.GetClassType(runtimePropertyType, options);
}
}
- public bool IgnoreNullValues { get; private set; }
+ public abstract bool TryCreateEnumerableAddMethod(object target, out object addMethodDelegate);
- public bool IsNullableType { get; private set; }
+ public abstract object CreateEnumerableAddMethod(MethodInfo addMethod, object target);
+
+ public abstract void AddObjectToEnumerableWithReflection(object addMethodDelegate, object value);
+
+ public abstract void AddObjectToParentEnumerable(object addMethodDelegate, object value);
+
+ public abstract void AddObjectToDictionary(object target, string key, object value);
+
+ public abstract void AddObjectToParentDictionary(object target, string key, object value);
+
+ public abstract bool CanPopulateDictionary(object target);
+
+ public abstract bool ParentDictionaryCanBePopulated(object target);
+
+ public bool IgnoreNullValues { get; private set; }
public bool IsPropertyPolicy { get; protected set; }
public Func<object, TDeclaredProperty> Get { get; private set; }
public Action<object, TDeclaredProperty> Set { get; private set; }
+ public Action<TDeclaredProperty> AddItemToEnumerable { get; private set; }
+
public JsonConverter<TConverter> Converter { get; internal set; }
public override void Initialize(
Type parentClassType,
Type declaredPropertyType,
Type runtimePropertyType,
- Type implementedPropertyType,
+ ClassType runtimeClassType,
PropertyInfo propertyInfo,
Type elementType,
JsonConverter converter,
+ bool treatAsNullable,
JsonSerializerOptions options)
{
- base.Initialize(parentClassType, declaredPropertyType, runtimePropertyType, implementedPropertyType, propertyInfo, elementType, converter, options);
-
- if (propertyInfo != null &&
- // We only want to get the getter and setter if we are going to use them.
- // If the declared type is not the property info type, then we are just
- // getting metadata on how best to (de)serialize derived types.
- declaredPropertyType == propertyInfo.PropertyType)
+ base.Initialize(
+ parentClassType,
+ declaredPropertyType,
+ runtimePropertyType,
+ runtimeClassType,
+ propertyInfo,
+ elementType,
+ converter,
+ treatAsNullable,
+ options);
+
+ if (propertyInfo != null)
{
if (propertyInfo.GetMethod?.IsPublic == true)
{
}
}
- public override IList CreateConverterList()
- {
- return new List<TDeclaredProperty>();
- }
+ private JsonPropertyInfo _elementPropertyInfo;
- public override Type GetConcreteType(Type parentType)
+ private void SetPropertyInfoForObjectElement()
{
- if (JsonClassInfo.IsDeserializedByAssigningFromList(parentType))
- {
- return typeof(List<TDeclaredProperty>);
- }
- else if (JsonClassInfo.IsSetInterface(parentType))
+ if (_elementPropertyInfo == null && ElementClassInfo.PolicyProperty == null)
{
- return typeof(HashSet<TDeclaredProperty>);
+ _elementPropertyInfo = ElementClassInfo.CreateRootObject(Options);
}
-
- return parentType;
}
- public override IEnumerable CreateDerivedEnumerableInstance(ref ReadStack state, JsonPropertyInfo collectionPropertyInfo, IList sourceList)
+ public override bool TryCreateEnumerableAddMethod(object target, out object addMethodDelegate)
{
- // Implementing types that don't have default constructors are not supported for deserialization.
- if (collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject == null)
- {
- throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
- collectionPropertyInfo.DeclaredPropertyType,
- collectionPropertyInfo.ParentClassType,
- collectionPropertyInfo.PropertyInfo);
- }
+ SetPropertyInfoForObjectElement();
+ Debug.Assert((_elementPropertyInfo ?? ElementClassInfo.PolicyProperty) != null);
- object instance = collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject();
+ addMethodDelegate = (_elementPropertyInfo ?? ElementClassInfo.PolicyProperty).CreateEnumerableAddMethod(RuntimeClassInfo.AddItemToObject, target);
+ return addMethodDelegate != null;
+ }
- if (instance is IList instanceOfIList)
- {
- if (!instanceOfIList.IsReadOnly)
- {
- foreach (object item in sourceList)
- {
- instanceOfIList.Add(item);
- }
- return instanceOfIList;
- }
- }
- else if (instance is ICollection<TDeclaredProperty> instanceOfICollection)
- {
- if (!instanceOfICollection.IsReadOnly)
- {
- foreach (TDeclaredProperty item in sourceList)
- {
- instanceOfICollection.Add(item);
- }
- return instanceOfICollection;
- }
- }
- else if (instance is Stack<TDeclaredProperty> instanceOfStack)
- {
- foreach (TDeclaredProperty item in sourceList)
- {
- instanceOfStack.Push(item);
- }
- return instanceOfStack;
- }
- else if (instance is Queue<TDeclaredProperty> instanceOfQueue)
+ public override object CreateEnumerableAddMethod(MethodInfo addMethod, object target)
+ {
+ if (target is ICollection<TDeclaredProperty> collection && collection.IsReadOnly)
{
- foreach (TDeclaredProperty item in sourceList)
- {
- instanceOfQueue.Enqueue(item);
- }
- return instanceOfQueue;
+ return null;
}
- // TODO (https://github.com/dotnet/corefx/issues/40479):
- // Use reflection to support types implementing Stack or Queue.
+ return Options.MemberAccessorStrategy.CreateAddDelegate<TDeclaredProperty>(addMethod, target);
+ }
- throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
- collectionPropertyInfo.DeclaredPropertyType,
- collectionPropertyInfo.ParentClassType,
- collectionPropertyInfo.PropertyInfo);
+ public override void AddObjectToEnumerableWithReflection(object addMethodDelegate, object value)
+ {
+ Debug.Assert((_elementPropertyInfo ?? ElementClassInfo.PolicyProperty) != null);
+ (_elementPropertyInfo ?? ElementClassInfo.PolicyProperty).AddObjectToParentEnumerable(addMethodDelegate, value);
}
- public override object CreateDerivedDictionaryInstance(ref ReadStack state, JsonPropertyInfo collectionPropertyInfo, IDictionary sourceDictionary)
+ public override void AddObjectToParentEnumerable(object addMethodDelegate, object value)
{
- // Implementing types that don't have default constructors are not supported for deserialization.
- if (collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject == null)
- {
- throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
- collectionPropertyInfo.DeclaredPropertyType,
- collectionPropertyInfo.ParentClassType,
- collectionPropertyInfo.PropertyInfo);
- }
+ ((Action<TDeclaredProperty>)addMethodDelegate)((TDeclaredProperty)value);
+ }
- object instance = collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject();
+ public override void AddObjectToDictionary(object target, string key, object value)
+ {
+ Debug.Assert((_elementPropertyInfo ?? ElementClassInfo.PolicyProperty) != null);
+ (_elementPropertyInfo ?? ElementClassInfo.PolicyProperty).AddObjectToParentDictionary(target, key, value);
+ }
- if (instance is IDictionary instanceOfIDictionary)
+ public override void AddObjectToParentDictionary(object target, string key, object value)
+ {
+ if (target is IDictionary<string, TDeclaredProperty> genericDict)
{
- if (!instanceOfIDictionary.IsReadOnly)
- {
- foreach (DictionaryEntry entry in sourceDictionary)
- {
- instanceOfIDictionary.Add((string)entry.Key, entry.Value);
- }
- return instanceOfIDictionary;
- }
+ Debug.Assert(!genericDict.IsReadOnly);
+ genericDict[key] = (TDeclaredProperty)value;
}
- else if (instance is IDictionary<string, TDeclaredProperty> instanceOfGenericIDictionary)
+ else
{
- if (!instanceOfGenericIDictionary.IsReadOnly)
- {
- foreach (DictionaryEntry entry in sourceDictionary)
- {
- instanceOfGenericIDictionary.Add((string)entry.Key, (TDeclaredProperty)entry.Value);
- }
- return instanceOfGenericIDictionary;
- }
+ throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(target.GetType(), parentType: null, memberInfo: null);
}
+ }
- // TODO (https://github.com/dotnet/corefx/issues/40479):
- // Use reflection to support types implementing SortedList and maybe immutable dictionaries.
-
- // Types implementing SortedList and immutable dictionaries will fail here.
- throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
- collectionPropertyInfo.DeclaredPropertyType,
- collectionPropertyInfo.ParentClassType,
- collectionPropertyInfo.PropertyInfo);
+ public override bool CanPopulateDictionary(object target)
+ {
+ SetPropertyInfoForObjectElement();
+ Debug.Assert((_elementPropertyInfo ?? ElementClassInfo.PolicyProperty) != null);
+ return (_elementPropertyInfo ?? ElementClassInfo.PolicyProperty).ParentDictionaryCanBePopulated(target);
}
- public override IEnumerable CreateIEnumerableInstance(ref ReadStack state, Type parentType, IList sourceList)
+ public override bool ParentDictionaryCanBePopulated(object target)
{
- if (parentType.IsGenericType)
+ if (target is IDictionary<string, TDeclaredProperty> genericDict && !genericDict.IsReadOnly)
{
- Type genericTypeDefinition = parentType.GetGenericTypeDefinition();
- IEnumerable<TDeclaredProperty> items = CreateGenericTDeclaredPropertyIEnumerable(sourceList);
-
- if (genericTypeDefinition == typeof(Stack<>))
- {
- return new Stack<TDeclaredProperty>(items);
- }
- else if (genericTypeDefinition == typeof(Queue<>))
- {
- return new Queue<TDeclaredProperty>(items);
- }
- else if (genericTypeDefinition == typeof(HashSet<>))
- {
- return new HashSet<TDeclaredProperty>(items);
- }
- else if (genericTypeDefinition == typeof(LinkedList<>))
- {
- return new LinkedList<TDeclaredProperty>(items);
- }
- else if (genericTypeDefinition == typeof(SortedSet<>))
- {
- return new SortedSet<TDeclaredProperty>(items);
- }
-
- return (IEnumerable)Activator.CreateInstance(parentType, items);
+ return true;
}
- else
+ else if (target is IDictionary dict && !dict.IsReadOnly)
{
- if (parentType == typeof(ArrayList))
- {
- return new ArrayList(sourceList);
- }
- // Stack and Queue go into this condition, until we support with reflection.
- else
+ Type genericDictType = target.GetType().GetInterface("System.Collections.Generic.IDictionary`2") ??
+ target.GetType().GetInterface("System.Collections.Generic.IReadOnlyDictionary`2");
+
+ if (genericDictType != null && genericDictType.GetGenericArguments()[0] != typeof(string))
{
- return (IEnumerable)Activator.CreateInstance(parentType, sourceList);
+ return false;
}
+
+ return true;
}
+
+ return false;
}
- public override IDictionary CreateIDictionaryInstance(ref ReadStack state, Type parentType, IDictionary sourceDictionary)
+ public override IList CreateConverterList()
{
- if (parentType.FullName == JsonClassInfo.HashtableTypeName)
- {
- return new Hashtable(sourceDictionary);
- }
- // SortedList goes into this condition, unless we add a ref to System.Collections.NonGeneric.
- else
- {
- return (IDictionary)Activator.CreateInstance(parentType, sourceDictionary);
- }
+ return new List<TDeclaredProperty>();
+ }
+
+ public override IDictionary CreateConverterDictionary()
+ {
+ return new Dictionary<string, TDeclaredProperty>();
}
// Creates an IEnumerable<TDeclaredPropertyType> and populates it with the items in the
return collection;
}
-
- private IEnumerable<TDeclaredProperty> CreateGenericTDeclaredPropertyIEnumerable(IList sourceList)
- {
- foreach (object item in sourceList)
- {
- yield return (TDeclaredProperty)item;
- }
- }
}
}
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType);
}
- if (state.Current.KeyName == null && state.Current.IsProcessingDictionaryOrIDictionaryConstructible())
+ if (state.Current.KeyName == null && state.Current.IsProcessingDictionary())
{
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType);
return;
Debug.Assert(current.CollectionEnumerator != null);
TConverter value;
+
if (current.CollectionEnumerator is IEnumerator<TConverter> enumerator)
{
// Avoid boxing for strongly-typed enumerators such as returned from IList<T>.
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType);
}
- if (state.Current.KeyName == null && state.Current.IsProcessingDictionaryOrIDictionaryConstructible())
+ if (state.Current.KeyName == null && state.Current.IsProcessingDictionary())
{
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType);
return;
key = enumerator.Current.Key;
value = enumerator.Current.Value;
}
- else if (current.IsIDictionaryConstructible || current.IsIDictionaryConstructibleProperty)
+ else
{
- key = (string)((DictionaryEntry)current.CollectionEnumerator.Current).Key;
- value = (TProperty?)((DictionaryEntry)current.CollectionEnumerator.Current).Value;
+ if (((DictionaryEntry)current.CollectionEnumerator.Current).Key is string keyAsString)
+ {
+ key = keyAsString;
+ value = (TProperty?)((DictionaryEntry)current.CollectionEnumerator.Current).Value;
+ }
+ else
+ {
+ throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
+ current.JsonPropertyInfo.DeclaredPropertyType,
+ current.JsonPropertyInfo.ParentClassType,
+ current.JsonPropertyInfo.PropertyInfo);
+ }
}
Debug.Assert(key != null);
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
using System.Text.Json.Serialization.Converters;
namespace System.Text.Json
{
public static partial class JsonSerializer
{
- private static void HandleStartArray(
- JsonSerializerOptions options,
- ref Utf8JsonReader reader,
- ref ReadStack state)
+ private static void HandleStartArray(JsonSerializerOptions options, ref ReadStack state)
{
if (state.Current.SkipProperty)
{
Debug.Assert(state.Current.JsonClassInfo.ClassType != ClassType.Value);
// Set or replace the existing enumerable value.
- object value = ReadStackFrame.CreateEnumerableValue(ref reader, ref state);
+ object value = ReadStackFrame.CreateEnumerableValue(ref state);
// If value is not null, then we don't have a converter so apply the value.
if (value != null)
{
+ state.Current.DetermineEnumerablePopulationStrategy(value);
+
if (state.Current.ReturnValue != null)
{
state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
}
else
{
- // Primitive arrays being returned without object
- state.Current.SetReturnValue(value);
+ state.Current.ReturnValue = value;
}
}
}
}
ApplyObjectToEnumerable(value, ref state);
-
return false;
}
}
else
{
- if (!(state.Current.ReturnValue is IList list))
+ if (state.Current.AddObjectToEnumerable == null)
{
- ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(value.GetType());
- return;
+ if (state.Current.ReturnValue is IList list)
+ {
+ list.Add(value);
+ }
+ else
+ {
+ ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(value.GetType());
+ return;
+ }
+ }
+ else
+ {
+ state.Current.JsonPropertyInfo.AddObjectToEnumerableWithReflection(state.Current.AddObjectToEnumerable, value);
}
- list.Add(value);
}
}
else if (!setPropertyDirectly && state.Current.IsProcessingProperty(ClassType.Enumerable))
{
Debug.Assert(state.Current.JsonPropertyInfo != null);
Debug.Assert(state.Current.ReturnValue != null);
+
if (state.Current.TempEnumerableValues != null)
{
state.Current.TempEnumerableValues.Add(value);
}
else
{
- IList list = (IList)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
- if (list == null ||
+ JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
+
+ object currentEnumerable = jsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
+ if (currentEnumerable == null ||
// ImmutableArray<T> is a struct, so default value won't be null.
- state.Current.JsonPropertyInfo.RuntimePropertyType.FullName.StartsWith(DefaultImmutableEnumerableConverter.ImmutableArrayGenericTypeName))
+ jsonPropertyInfo.IsImmutableArray)
+ {
+ jsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
+ }
+ else if (state.Current.AddObjectToEnumerable == null)
{
- state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
+ ((IList)currentEnumerable).Add(value);
}
else
{
- list.Add(value);
+ jsonPropertyInfo.AddObjectToEnumerableWithReflection(state.Current.AddObjectToEnumerable, value);
}
}
- }
- else if (state.Current.IsProcessingObject(ClassType.Dictionary) ||
- (state.Current.IsProcessingProperty(ClassType.Dictionary) && !setPropertyDirectly))
- {
- Debug.Assert(state.Current.ReturnValue != null);
- IDictionary dictionary = (IDictionary)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
- string key = state.Current.KeyName;
- Debug.Assert(!string.IsNullOrEmpty(key));
- dictionary[key] = value;
}
- else if (state.Current.IsProcessingObject(ClassType.IDictionaryConstructible) ||
- (state.Current.IsProcessingProperty(ClassType.IDictionaryConstructible) && !setPropertyDirectly))
+ else if (state.Current.IsProcessingObject(ClassType.Dictionary) || (state.Current.IsProcessingProperty(ClassType.Dictionary) && !setPropertyDirectly))
{
- Debug.Assert(state.Current.TempDictionaryValues != null);
- IDictionary dictionary = (IDictionary)state.Current.TempDictionaryValues;
-
string key = state.Current.KeyName;
Debug.Assert(!string.IsNullOrEmpty(key));
- dictionary[key] = value;
+
+ if (state.Current.TempDictionaryValues != null)
+ {
+ (state.Current.TempDictionaryValues)[key] = value;
+ }
+ else
+ {
+ Debug.Assert(state.Current.ReturnValue != null);
+
+ object currentDictionary = state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
+
+ if (currentDictionary is IDictionary dict)
+ {
+ Debug.Assert(!dict.IsReadOnly);
+ dict[key] = value;
+ }
+ else
+ {
+ state.Current.JsonPropertyInfo.AddObjectToDictionary(currentDictionary, key, value);
+ }
+ }
}
else
{
}
else
{
- ((IList<TProperty>)state.Current.ReturnValue).Add(value);
+ AddValueToEnumerable(ref state, state.Current.ReturnValue, value);
}
}
else if (state.Current.IsProcessingProperty(ClassType.Enumerable))
{
- Debug.Assert(state.Current.JsonPropertyInfo != null);
- Debug.Assert(state.Current.ReturnValue != null);
if (state.Current.TempEnumerableValues != null)
{
((IList<TProperty>)state.Current.TempEnumerableValues).Add(value);
}
else
{
- IList<TProperty> list = (IList<TProperty>)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
- if (list == null)
+ Debug.Assert(state.Current.JsonPropertyInfo != null);
+ Debug.Assert(state.Current.ReturnValue != null);
+
+ JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
+
+ object currentEnumerable = jsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
+ if (currentEnumerable == null ||
+ // ImmutableArray<T> is a struct, so default value won't be null.
+ jsonPropertyInfo.IsImmutableArray)
{
- state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
+ jsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
}
else
{
- list.Add(value);
+ AddValueToEnumerable(ref state, currentEnumerable, value);
}
}
}
else if (state.Current.IsProcessingDictionary())
{
- Debug.Assert(state.Current.ReturnValue != null);
- IDictionary<string, TProperty> dictionary = (IDictionary<string, TProperty>)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
-
string key = state.Current.KeyName;
Debug.Assert(!string.IsNullOrEmpty(key));
- dictionary[key] = value;
- }
- else if (state.Current.IsProcessingIDictionaryConstructible())
- {
- Debug.Assert(state.Current.TempDictionaryValues != null);
- IDictionary<string, TProperty> dictionary = (IDictionary<string, TProperty>)state.Current.TempDictionaryValues;
- string key = state.Current.KeyName;
- Debug.Assert(!string.IsNullOrEmpty(key));
- dictionary[key] = value;
+ if (state.Current.TempDictionaryValues != null)
+ {
+ ((IDictionary<string, TProperty>)state.Current.TempDictionaryValues)[key] = value;
+ }
+ else
+ {
+ Debug.Assert(state.Current.ReturnValue != null);
+
+ object currentDictionary = state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
+
+ if (currentDictionary is IDictionary<string, TProperty> genericDict)
+ {
+ Debug.Assert(!genericDict.IsReadOnly);
+ genericDict[key] = value;
+ }
+ else if (currentDictionary is IDictionary dict)
+ {
+ Debug.Assert(!dict.IsReadOnly);
+ dict[key] = value;
+ }
+ else
+ {
+ throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(currentDictionary.GetType(), parentType: null, memberInfo: null);
+ }
+ }
}
else
{
state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
}
}
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void AddValueToEnumerable<TProperty>(ref ReadStack state, object target, TProperty value)
+ {
+ if (target is IList<TProperty> genericList)
+ {
+ Debug.Assert(!genericList.IsReadOnly);
+ genericList.Add(value);
+ }
+ else if (target is IList list)
+ {
+ Debug.Assert(!list.IsReadOnly);
+ list.Add(value);
+ }
+ else
+ {
+ Debug.Assert(state.Current.AddObjectToEnumerable != null);
+ ((Action<TProperty>)state.Current.AddObjectToEnumerable)(value);
+ }
+ }
}
}
JsonClassInfo classInfo = state.Current.JsonClassInfo;
- if (state.Current.IsProcessingIDictionaryConstructible())
- {
- state.Current.TempDictionaryValues = (IDictionary)classInfo.CreateConcreteDictionary();
- }
- else
+ if (state.Current.IsProcessingEnumerable())
{
if (classInfo.CreateObject == null)
{
}
state.Current.ReturnValue = classInfo.CreateObject();
}
+ else if (state.Current.IsProcessingDictionary())
+ {
+ object dictValue = ReadStackFrame.CreateDictionaryValue(ref state);
+
+ // If value is not null, then we don't have a converter so apply the value.
+ if (dictValue != null)
+ {
+ state.Current.ReturnValue = dictValue;
+ state.Current.DetermineIfDictionaryCanBePopulated(state.Current.ReturnValue);
+ }
+ }
+ else
+ {
+ state.Current.ReturnValue = classInfo.CreateObject();
+ }
return;
}
state.Current.CollectionPropertyInitialized = true;
- if (state.Current.IsProcessingIDictionaryConstructible())
+ object value = ReadStackFrame.CreateDictionaryValue(ref state);
+ if (value != null)
{
- JsonClassInfo dictionaryClassInfo;
- if (jsonPropertyInfo.DeclaredPropertyType == jsonPropertyInfo.ImplementedPropertyType)
+ state.Current.DetermineIfDictionaryCanBePopulated(value);
+
+ if (state.Current.ReturnValue != null)
{
- dictionaryClassInfo = options.GetOrAddClass(jsonPropertyInfo.RuntimePropertyType);
+ state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
}
else
{
- dictionaryClassInfo = options.GetOrAddClass(jsonPropertyInfo.DeclaredPropertyType);
- }
-
- state.Current.TempDictionaryValues = (IDictionary)dictionaryClassInfo.CreateConcreteDictionary();
- }
- else
- {
- // Create the dictionary.
- JsonClassInfo dictionaryClassInfo = jsonPropertyInfo.RuntimeClassInfo;
- IDictionary value = (IDictionary)dictionaryClassInfo.CreateObject();
-
- if (value != null)
- {
- if (state.Current.ReturnValue != null)
- {
- state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
- }
- else
- {
- // A dictionary is being returned directly, or a nested dictionary.
- state.Current.SetReturnValue(value);
- }
+ // A dictionary is being returned directly, or a nested dictionary.
+ state.Current.ReturnValue = value;
}
}
}
if (state.Current.IsProcessingProperty(ClassType.Dictionary))
{
- // Handle special case of DataExtensionProperty where we just added a dictionary element to the extension property.
- // Since the JSON value is not a dictionary element (it's a normal property in JSON) a JsonTokenType.EndObject
- // encountered here is from the outer object so forward to HandleEndObject().
- if (state.Current.JsonClassInfo.DataExtensionProperty == state.Current.JsonPropertyInfo)
+ if (state.Current.TempDictionaryValues != null)
{
- HandleEndObject(ref state);
+ JsonDictionaryConverter converter = state.Current.JsonPropertyInfo.DictionaryConverter;
+ state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, converter.CreateFromDictionary(ref state, state.Current.TempDictionaryValues, options));
+ state.Current.EndProperty();
}
else
{
- // We added the items to the dictionary already.
- state.Current.EndProperty();
+ // Handle special case of DataExtensionProperty where we just added a dictionary element to the extension property.
+ // Since the JSON value is not a dictionary element (it's a normal property in JSON) a JsonTokenType.EndObject
+ // encountered here is from the outer object so forward to HandleEndObject().
+ if (state.Current.JsonClassInfo.DataExtensionProperty == state.Current.JsonPropertyInfo)
+ {
+ HandleEndObject(ref state);
+ }
+ else
+ {
+ // We added the items to the dictionary already.
+ state.Current.EndProperty();
+ }
}
}
- else if (state.Current.IsProcessingProperty(ClassType.IDictionaryConstructible))
- {
- Debug.Assert(state.Current.TempDictionaryValues != null);
- JsonDictionaryConverter converter = state.Current.JsonPropertyInfo.DictionaryConverter;
- state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, converter.CreateFromDictionary(ref state, state.Current.TempDictionaryValues, options));
- state.Current.EndProperty();
- }
else
{
object value;
{
public static partial class JsonSerializer
{
- private static bool HandleNull(ref Utf8JsonReader reader, ref ReadStack state)
+ private static bool HandleNull(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state)
{
if (state.Current.SkipProperty)
{
JsonPropertyInfo elementPropertyInfo = jsonPropertyInfo.ElementClassInfo.PolicyProperty;
// if elementPropertyInfo == null then this element doesn't need a converter (an object).
-
if (elementPropertyInfo?.CanBeNull == false)
{
// Allow a value type converter to return a null value representation.
{
private static void HandleStartObject(JsonSerializerOptions options, ref ReadStack state)
{
- Debug.Assert(!state.Current.IsProcessingDictionaryOrIDictionaryConstructible());
+ Debug.Assert(!state.Current.IsProcessingDictionary());
if (state.Current.IsProcessingEnumerable())
{
}
}
- if (state.Current.IsProcessingIDictionaryConstructible())
+ if (state.Current.IsProcessingObject(ClassType.Dictionary))
{
- state.Current.TempDictionaryValues = (IDictionary)classInfo.CreateConcreteDictionary();
+ object value = ReadStackFrame.CreateDictionaryValue(ref state);
+
+ // If value is not null, then we don't have a converter so apply the value.
+ if (value != null)
+ {
+ state.Current.ReturnValue = value;
+ state.Current.DetermineIfDictionaryCanBePopulated(state.Current.ReturnValue);
+ }
}
else
{
private static void HandleEndObject(ref ReadStack state)
{
// Only allow dictionaries to be processed here if this is the DataExtensionProperty.
- Debug.Assert(
- (!state.Current.IsProcessingDictionary() || state.Current.JsonClassInfo.DataExtensionProperty == state.Current.JsonPropertyInfo) &&
- !state.Current.IsProcessingIDictionaryConstructible());
+ Debug.Assert(!state.Current.IsProcessingDictionary() || state.Current.JsonClassInfo.DataExtensionProperty == state.Current.JsonPropertyInfo);
// Check if we are trying to build the sorted cache.
if (state.Current.PropertyRefCache != null)
else
{
state.Pop();
+
ApplyObjectToEnumerable(value, ref state);
}
}
return;
}
- Debug.Assert(state.Current.ReturnValue != default || state.Current.TempDictionaryValues != default);
- Debug.Assert(state.Current.JsonClassInfo != default);
+ Debug.Assert(state.Current.ReturnValue != null || state.Current.TempDictionaryValues != null);
+ Debug.Assert(state.Current.JsonClassInfo != null);
- bool isProcessingDictObject = state.Current.IsProcessingDictionaryOrIDictionaryConstructibleObject();
- if ((isProcessingDictObject || state.Current.IsProcessingDictionaryOrIDictionaryConstructibleProperty()) &&
+ bool isProcessingDictObject = state.Current.IsProcessingObject(ClassType.Dictionary);
+ if ((isProcessingDictObject || state.Current.IsProcessingProperty(ClassType.Dictionary)) &&
state.Current.JsonClassInfo.DataExtensionProperty != state.Current.JsonPropertyInfo)
{
if (isProcessingDictObject)
break;
}
}
- else if (readStack.Current.IsProcessingDictionaryOrIDictionaryConstructible())
+ else if (readStack.Current.IsProcessingDictionary())
{
HandleStartDictionary(options, ref readStack);
}
// A non-dictionary property can also have EndProperty() called when completed, although it is redundant.
readStack.Current.EndProperty();
}
- else if (readStack.Current.IsProcessingDictionaryOrIDictionaryConstructible())
+ else if (readStack.Current.IsProcessingDictionary())
{
HandleEndDictionary(options, ref readStack);
}
{
if (!readStack.Current.IsProcessingValue())
{
- HandleStartArray(options, ref reader, ref readStack);
+ HandleStartArray(options, ref readStack);
}
else if (!HandleObjectAsValue(tokenType, options, ref reader, ref readStack, ref initialState, initialBytesConsumed))
{
}
else if (tokenType == JsonTokenType.Null)
{
- HandleNull(ref reader, ref readStack);
+ HandleNull(options, ref reader, ref readStack);
}
}
}
key = polymorphicEnumerator.Current.Key;
value = (TProperty)polymorphicEnumerator.Current.Value;
}
- else if (current.IsIDictionaryConstructible || current.IsIDictionaryConstructibleProperty)
- {
- key = (string)((DictionaryEntry)current.CollectionEnumerator.Current).Key;
- value = (TProperty)((DictionaryEntry)current.CollectionEnumerator.Current).Value;
- }
else
{
- // Todo: support non-generic Dictionary here (IDictionaryEnumerator)
- // https://github.com/dotnet/corefx/issues/41034
- throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
- current.JsonPropertyInfo.DeclaredPropertyType,
- current.JsonPropertyInfo.ParentClassType,
- current.JsonPropertyInfo.PropertyInfo);
+ if (((DictionaryEntry)current.CollectionEnumerator.Current).Key is string keyAsString)
+ {
+ key = keyAsString;
+ value = (TProperty)((DictionaryEntry)current.CollectionEnumerator.Current).Value;
+ }
+ else
+ {
+ throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
+ current.JsonPropertyInfo.DeclaredPropertyType,
+ current.JsonPropertyInfo.ParentClassType,
+ current.JsonPropertyInfo.PropertyInfo);
+ }
}
if (value == null)
return;
}
- // A property that returns a type that is deserialized by passing an
- // IDictionary to its constructor keeps the same stack frame.
- if (jsonPropertyInfo.ClassType == ClassType.IDictionaryConstructible)
- {
- state.Current.IsIDictionaryConstructibleProperty = true;
-
- bool endOfEnumerable = HandleDictionary(jsonPropertyInfo.ElementClassInfo, options, writer, ref state);
- if (endOfEnumerable)
- {
- state.Current.MoveToNextProperty = true;
- }
-
- return;
- }
-
// A property that returns an object.
if (!obtainedValue)
{
finishedSerializing = true;
break;
case ClassType.Dictionary:
- case ClassType.IDictionaryConstructible:
finishedSerializing = HandleDictionary(state.Current.JsonClassInfo.ElementClassInfo, options, writer, ref state);
break;
default:
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Text.Json.Serialization;
internal static readonly JsonSerializerOptions s_defaultOptions = new JsonSerializerOptions();
private readonly ConcurrentDictionary<Type, JsonClassInfo> _classes = new ConcurrentDictionary<Type, JsonClassInfo>();
- private readonly ConcurrentDictionary<Type, JsonPropertyInfo> _objectJsonProperties = new ConcurrentDictionary<Type, JsonPropertyInfo>();
private static readonly ConcurrentDictionary<string, ImmutableCollectionCreator> s_createRangeDelegates = new ConcurrentDictionary<string, ImmutableCollectionCreator>();
private MemberAccessor _memberAccessorStrategy;
private JsonNamingPolicy _dictionayKeyPolicy;
};
}
- internal JsonPropertyInfo GetJsonPropertyInfoFromClassInfo(Type objectType, JsonSerializerOptions options)
- {
- if (!_objectJsonProperties.TryGetValue(objectType, out JsonPropertyInfo propertyInfo))
- {
- propertyInfo = JsonClassInfo.CreateProperty(
- objectType,
- objectType,
- objectType,
- propertyInfo: null,
- typeof(object),
- converter: null,
- options);
- _objectJsonProperties[objectType] = propertyInfo;
- }
-
- return propertyInfo;
- }
-
internal bool CreateRangeDelegatesContainsKey(string key)
{
return s_createRangeDelegates.ContainsKey(key);
return s_createRangeDelegates.TryAdd(key, createRangeDelegate);
}
-
internal void VerifyMutable()
{
// The default options are hidden and thus should be immutable.
{
public abstract JsonClassInfo.ConstructorDelegate CreateConstructor(Type classType);
+ public abstract Action<TProperty> CreateAddDelegate<TProperty>(MethodInfo addMethod, object target);
+
public abstract ImmutableCollectionCreator ImmutableCollectionCreateRange(Type constructingType, Type collectionType, Type elementType);
public abstract ImmutableCollectionCreator ImmutableDictionaryCreateRange(Type constructingType, Type collectionType, Type elementType);
}
else if (frame.IsProcessingEnumerable())
{
- // For enumerables add the index.
IList list = frame.TempEnumerableValues;
if (list == null && frame.ReturnValue != null)
{
+
list = (IList)frame.JsonPropertyInfo?.GetValueAsObject(frame.ReturnValue);
}
-
if (list != null)
{
sb.Append(@"[");
// Current property values.
public JsonPropertyInfo JsonPropertyInfo;
+ // Delegate used to add elements to the current property.
+ public object AddObjectToEnumerable;
+
// Support System.Array and other types that don't implement IList.
public IList TempEnumerableValues;
public List<PropertyRef> PropertyRefCache;
/// <summary>
- /// Is the current object an Enumerable, Dictionary or IDictionaryConstructible.
+ /// Is the current object an Enumerable or Dictionary.
/// </summary>
public bool IsProcessingCollectionObject()
{
- return IsProcessingObject(ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible);
+ return IsProcessingObject(ClassType.Enumerable | ClassType.Dictionary);
}
/// <summary>
- /// Is the current property an Enumerable, Dictionary or IDictionaryConstructible.
+ /// Is the current property an Enumerable or Dictionary.
/// </summary>
public bool IsProcessingCollectionProperty()
{
- return IsProcessingProperty(ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible);
+ return IsProcessingProperty(ClassType.Enumerable | ClassType.Dictionary);
}
/// <summary>
- /// Is the current object or property an Enumerable, Dictionary or IDictionaryConstructible.
+ /// Is the current object or property an Enumerable or Dictionary.
/// </summary>
public bool IsProcessingCollection()
{
- return IsProcessingObject(ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible) ||
- IsProcessingProperty(ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible);
+ return IsProcessingObject(ClassType.Enumerable | ClassType.Dictionary) ||
+ IsProcessingProperty(ClassType.Enumerable | ClassType.Dictionary);
}
/// <summary>
}
/// <summary>
- /// Is the current object or property an IDictionaryConstructible.
- /// </summary>
- public bool IsProcessingIDictionaryConstructible()
- {
- return IsProcessingObject(ClassType.IDictionaryConstructible)
- || IsProcessingProperty(ClassType.IDictionaryConstructible);
- }
-
- /// <summary>
- /// Is the current object a Dictionary or IDictionaryConstructible.
- /// </summary>
- public bool IsProcessingDictionaryOrIDictionaryConstructibleObject()
- {
- return IsProcessingObject(ClassType.Dictionary | ClassType.IDictionaryConstructible);
- }
-
- /// <summary>
- /// Is the current property a Dictionary or IDictionaryConstructible.
- /// </summary>
- public bool IsProcessingDictionaryOrIDictionaryConstructibleProperty()
- {
- return IsProcessingProperty(ClassType.Dictionary | ClassType.IDictionaryConstructible);
- }
-
- /// <summary>
- /// Is the current object or property a Dictionary or IDictionaryConstructible.
- /// </summary>
- public bool IsProcessingDictionaryOrIDictionaryConstructible()
- {
- return IsProcessingObject(ClassType.Dictionary | ClassType.IDictionaryConstructible) ||
- IsProcessingProperty(ClassType.Dictionary | ClassType.IDictionaryConstructible);
- }
-
- /// <summary>
/// Is the current object or property an Enumerable.
/// </summary>
public bool IsProcessingEnumerable()
public void InitializeJsonPropertyInfo()
{
- if (IsProcessingObject(ClassType.Value | ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible))
+ if (IsProcessingObject(ClassType.Value | ClassType.Enumerable | ClassType.Dictionary))
{
JsonPropertyInfo = JsonClassInfo.PolicyProperty;
}
public void EndProperty()
{
+ AddObjectToEnumerable = null;
CollectionPropertyInitialized = false;
JsonPropertyInfo = null;
TempEnumerableValues = null;
KeyName = null;
}
- public static object CreateEnumerableValue(ref Utf8JsonReader reader, ref ReadStack state)
+ public static object CreateEnumerableValue(ref ReadStack state)
{
JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
// Clear the value if present to ensure we don't confuse tempEnumerableValues with the collection.
if (!jsonPropertyInfo.IsPropertyPolicy &&
- !state.Current.JsonPropertyInfo.RuntimePropertyType.FullName.StartsWith(DefaultImmutableEnumerableConverter.ImmutableArrayGenericTypeName))
+ !state.Current.JsonPropertyInfo.IsImmutableArray)
{
jsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, null);
}
return null;
}
- Type propertyType = jsonPropertyInfo.RuntimePropertyType;
- if (typeof(IList).IsAssignableFrom(propertyType))
+ JsonClassInfo runtimeClassInfo = jsonPropertyInfo.RuntimeClassInfo;
+ if (runtimeClassInfo.CreateObject != null)
+ {
+ return runtimeClassInfo.CreateObject();
+ }
+ else
{
- // If IList, add the members as we create them.
- JsonClassInfo collectionClassInfo;
+ // Could not create an instance to be returned. For derived types, this means there is no parameterless ctor.
+ throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
+ jsonPropertyInfo.DeclaredPropertyType,
+ jsonPropertyInfo.ParentClassType,
+ jsonPropertyInfo.PropertyInfo);
+ }
+ }
- if (jsonPropertyInfo.DeclaredPropertyType == jsonPropertyInfo.ImplementedPropertyType)
+ public static object CreateDictionaryValue(ref ReadStack state)
+ {
+ JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
+
+ // If the property has a DictionaryConverter, then we use tempDictionaryValues.
+ if (jsonPropertyInfo.DictionaryConverter != null)
+ {
+ IDictionary converterDictionary;
+ JsonClassInfo elementClassInfo = jsonPropertyInfo.ElementClassInfo;
+ if (elementClassInfo.ClassType == ClassType.Value)
{
- collectionClassInfo = jsonPropertyInfo.RuntimeClassInfo;
+ converterDictionary = elementClassInfo.PolicyProperty.CreateConverterDictionary();
}
else
{
- collectionClassInfo = jsonPropertyInfo.DeclaredTypeClassInfo;
+ converterDictionary = new Dictionary<string, object>();
}
- if (collectionClassInfo.CreateObject() is IList collection)
- {
- return collection;
- }
- else
+ state.Current.TempDictionaryValues = converterDictionary;
+
+ // Clear the value if present to ensure we don't confuse tempEnumerableValues with the collection.
+ if (!jsonPropertyInfo.IsPropertyPolicy)
{
- ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(jsonPropertyInfo.DeclaredPropertyType);
- return null;
+ jsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, null);
}
+
+ return null;
+ }
+
+ JsonClassInfo runtimeClassInfo = jsonPropertyInfo.RuntimeClassInfo;
+ if (runtimeClassInfo.CreateObject != null)
+ {
+ return runtimeClassInfo.CreateObject();
}
else
{
- ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(propertyType);
- return null;
+ // Could not create an instance to be returned. For derived types, this means there is no parameterless ctor.
+ throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
+ jsonPropertyInfo.DeclaredPropertyType,
+ jsonPropertyInfo.ParentClassType,
+ jsonPropertyInfo.PropertyInfo);
}
}
return current.TempEnumerableValues;
}
- public void SetReturnValue(object value)
+ public void DetermineEnumerablePopulationStrategy(object targetEnumerable)
+ {
+ if (JsonPropertyInfo.RuntimeClassInfo.AddItemToObject != null)
+ {
+ if (!JsonPropertyInfo.TryCreateEnumerableAddMethod(targetEnumerable, out object addMethodDelegate))
+ {
+ // No "add" method for this collection, hence, not supported for deserialization.
+ throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
+ JsonPropertyInfo.DeclaredPropertyType,
+ JsonPropertyInfo.ParentClassType,
+ JsonPropertyInfo.PropertyInfo);
+ }
+
+ AddObjectToEnumerable = addMethodDelegate;
+ }
+ else if (targetEnumerable is IList targetList)
+ {
+ if (targetList.IsReadOnly)
+ {
+ throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
+ JsonPropertyInfo.DeclaredPropertyType,
+ JsonPropertyInfo.ParentClassType,
+ JsonPropertyInfo.PropertyInfo);
+ }
+ }
+ // If there's no add method, and we can't cast to IList, this collection is not supported for deserialization.
+ else
+ {
+ throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
+ JsonPropertyInfo.DeclaredPropertyType,
+ JsonPropertyInfo.ParentClassType,
+ JsonPropertyInfo.PropertyInfo);
+ }
+ }
+
+ public void DetermineIfDictionaryCanBePopulated(object targetDictionary)
{
- Debug.Assert(ReturnValue == null);
- ReturnValue = value;
+ if (!JsonPropertyInfo.CanPopulateDictionary(targetDictionary))
+ {
+ throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(
+ JsonPropertyInfo.DeclaredPropertyType,
+ JsonPropertyInfo.ParentClassType,
+ JsonPropertyInfo.PropertyInfo);
+ }
}
public bool SkipProperty => Drain ||
return (JsonClassInfo.ConstructorDelegate)dynamicMethod.CreateDelegate(typeof(JsonClassInfo.ConstructorDelegate));
}
+ public override Action<TProperty> CreateAddDelegate<TProperty>(MethodInfo addMethod, object target)
+ {
+ Debug.Assert(addMethod != null && target != null);
+ return (Action<TProperty>)addMethod.CreateDelegate(typeof(Action<TProperty>), target);
+ }
public override ImmutableCollectionCreator ImmutableCollectionCreateRange(Type constructingType, Type collectionType, Type elementType)
{
public override ImmutableCollectionCreator ImmutableDictionaryCreateRange(Type constructingType, Type collectionType, Type elementType)
{
+ Debug.Assert(collectionType.IsGenericType);
+
+ // Only string keys are allowed.
+ if (collectionType.GetGenericArguments()[0] != typeof(string))
+ {
+ throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(collectionType, parentType: null, memberInfo: null);
+ }
+
MethodInfo createRange = ImmutableDictionaryCreateRangeMethod(constructingType, elementType);
if (createRange == null)
return () => Activator.CreateInstance(type);
}
+ public override Action<TProperty> CreateAddDelegate<TProperty>(MethodInfo addMethod, object target)
+ {
+ Debug.Assert(addMethod != null && target != null);
+ return (Action<TProperty>)addMethod.CreateDelegate(typeof(Action<TProperty>), target);
+ }
+
public override ImmutableCollectionCreator ImmutableCollectionCreateRange(Type constructingType, Type collectionType, Type elementType)
{
MethodInfo createRange = ImmutableCollectionCreateRangeMethod(constructingType, elementType);
public override ImmutableCollectionCreator ImmutableDictionaryCreateRange(Type constructingType, Type collectionType, Type elementType)
{
+ Debug.Assert(collectionType.IsGenericType);
+
+ // Only string keys are allowed.
+ if (collectionType.GetGenericArguments()[0] != typeof(string))
+ {
+ throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(collectionType, parentType: null, memberInfo: null);
+ }
+
MethodInfo createRange = ImmutableDictionaryCreateRangeMethod(constructingType, elementType);
if (createRange == null)
Current.PopStackOnEndCollection = true;
Current.JsonPropertyInfo = Current.JsonClassInfo.PolicyProperty;
}
- else if (classType == ClassType.IDictionaryConstructible)
- {
- Current.PopStackOnEndCollection = true;
- Current.JsonPropertyInfo = Current.JsonClassInfo.PolicyProperty;
-
- Current.IsIDictionaryConstructible = true;
- }
else
{
Debug.Assert(nextClassInfo.ClassType == ClassType.Object || nextClassInfo.ClassType == ClassType.Unknown);
public IEnumerator CollectionEnumerator;
// Note all bools are kept together for packing:
public bool PopStackOnEndCollection;
- public bool IsIDictionaryConstructible;
- public bool IsIDictionaryConstructibleProperty;
// The current object.
public bool PopStackOnEndObject;
public void Initialize(Type type, JsonSerializerOptions options)
{
JsonClassInfo = options.GetOrAddClass(type);
- if (JsonClassInfo.ClassType == ClassType.Value || JsonClassInfo.ClassType == ClassType.Enumerable || JsonClassInfo.ClassType == ClassType.Dictionary)
+ if ((JsonClassInfo.ClassType & (ClassType.Value | ClassType.Enumerable | ClassType.Dictionary)) != 0)
{
JsonPropertyInfo = JsonClassInfo.PolicyProperty;
}
- else if (JsonClassInfo.ClassType == ClassType.IDictionaryConstructible)
- {
- JsonPropertyInfo = JsonClassInfo.PolicyProperty;
- IsIDictionaryConstructible = true;
- }
}
public void WriteObjectOrArrayStart(ClassType classType, Utf8JsonWriter writer, JsonSerializerOptions options, bool writeNull = false)
Debug.Assert(writeNull == false);
// Write start without a property name.
- if (classType == ClassType.Object || classType == ClassType.Dictionary || classType == ClassType.IDictionaryConstructible)
+ if (classType == ClassType.Object || classType == ClassType.Dictionary)
{
writer.WriteStartObject();
StartObjectWritten = true;
{
writer.WriteNull(propertyName);
}
- else if (classType == ClassType.Object ||
- classType == ClassType.Dictionary ||
- classType == ClassType.IDictionaryConstructible)
+ else if ((classType & (ClassType.Object | ClassType.Dictionary)) != 0)
{
writer.WriteStartObject(propertyName);
StartObjectWritten = true;
CurrentValue = null;
CollectionEnumerator = null;
ExtensionDataStatus = ExtensionDataWriteStatus.NotStarted;
- IsIDictionaryConstructible = false;
JsonClassInfo = null;
PropertyEnumeratorIndex = 0;
PopStackOnEndCollection = false;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void EndProperty()
{
- IsIDictionaryConstructibleProperty = false;
JsonPropertyInfo = null;
KeyName = null;
MoveToNextProperty = false;
using System.Globalization;
using Xunit;
-using System.Text.Json.Serialization;
namespace System.Text.Json.Tests
{
UnsupportedDerivedTypesWrapper_IEnumerable wrapper = new UnsupportedDerivedTypesWrapper_IEnumerable
{
- IEnumerableWrapper = new StringIEnumerableWrapper() { "1", "2", "3" },
+ IEnumerableWrapper = new StringIEnumerableWrapper(new List<string> { "1", "2", "3" }),
};
// Without converter, we throw on deserialize.
{
IDictionary ht = new Hashtable();
ht.Add("Key", "Value");
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(ht));
+ Assert.Equal(@"{""Key"":""Value""}", JsonSerializer.Serialize(ht));
}
}
Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(obj1));
ClassWithInvalidExtensionPropertyObjectString obj2 = new ClassWithInvalidExtensionPropertyObjectString();
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(obj2));
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(obj2));
}
private class ClassWithExtensionPropertyAlreadyInstantiated
}
}";
- // Unsupported collections will throw by default.
+ // Unsupported collections will throw on deserialize by default.
Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithUnsupportedDictionary>(json));
+
// Using new options instance to prevent using previously cached metadata.
JsonSerializerOptions options = new JsonSerializerOptions();
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(new ClassWithUnsupportedDictionary(), options));
+ string serialized = JsonSerializer.Serialize(new ClassWithUnsupportedDictionary(), options);
+
+ // Object keys are fine on serialization if the keys are strings.
+ Assert.Contains(@"""MyConcurrentDict"":null", serialized);
+ Assert.Contains(@"""MyIDict"":null", serialized);
+ Assert.Contains(@"""MyDict"":null", serialized);
+
+ // Unsupported collections will throw on deserialize by default.
options = new JsonSerializerOptions();
Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<WrapperForClassWithUnsupportedDictionary>(wrapperJson, options));
+
options = new JsonSerializerOptions();
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(new WrapperForClassWithUnsupportedDictionary(), options));
+ serialized = JsonSerializer.Serialize(new WrapperForClassWithUnsupportedDictionary(), options);
+
+ // Object keys are fine on serialization if the keys are strings.
+ Assert.Contains(@"{""MyClass"":{", serialized);
+ Assert.Contains(@"""MyConcurrentDict"":null", serialized);
+ Assert.Contains(@"""MyIDict"":null", serialized);
+ Assert.Contains(@"""MyDict"":null", serialized);
+ Assert.Contains("}}", serialized);
// When ignored, we can serialize and deserialize without exceptions.
options = new JsonSerializerOptions();
// Call only when testing serialization.
public void Initialize()
{
- MyStringIEnumerableWrapper = new StringIEnumerableWrapper() { "Hello" };
+ MyStringIEnumerableWrapper = new StringIEnumerableWrapper(new List<string>{ "Hello" });
}
}
// Call only when testing serialization.
public void Initialize()
{
- MyStringIReadOnlyCollectionWrapper = new StringIReadOnlyCollectionWrapper() { "Hello" };
+ MyStringIReadOnlyCollectionWrapper = new StringIReadOnlyCollectionWrapper(new List<string> { "Hello" });
}
}
// Call only when testing serialization.
public void Initialize()
{
- MyStringIReadOnlyListWrapper = new StringIReadOnlyListWrapper() { "Hello" };
+ MyStringIReadOnlyListWrapper = new StringIReadOnlyListWrapper(new List<string> { "Hello" });
}
}
{
private readonly List<string> _list = new List<string>();
- // For populating test data only. We can't rely on this method for real input.
- public void Add(string item)
+ public StringIEnumerableWrapper() { }
+
+ public StringIEnumerableWrapper(List<string> items)
{
- _list.Add(item);
+ _list = items;
}
public IEnumerator<string> GetEnumerator()
{
private readonly List<T> _list = new List<T>();
- public void Add(T item)
+ public GenericIEnumerableWrapper() { }
+
+ public GenericIEnumerableWrapper(List<T> items)
{
- _list.Add(item);
+ _list = items;
}
public IEnumerator<T> GetEnumerator()
public virtual bool IsReadOnly => ((ICollection<string>)_list).IsReadOnly;
- public void Add(string item)
+ public virtual void Add(string item)
{
_list.Add(item);
}
public virtual bool IsReadOnly => ((IList<string>)_list).IsReadOnly;
- public void Add(string item)
+ public virtual void Add(string item)
{
_list.Add(item);
}
{
private readonly List<string> _list = new List<string>();
- // For populating test data only. We cannot assume actual input will have this method.
- public void Add(string item)
+ public StringIReadOnlyCollectionWrapper() { }
+
+ public StringIReadOnlyCollectionWrapper(List<string> list)
{
- _list.Add(item);
+ _list = list;
}
public int Count => _list.Count;
{
private readonly List<T> _list = new List<T>();
- public void Add(T item)
+ public GenericIReadOnlyCollectionWrapper() { }
+
+ public GenericIReadOnlyCollectionWrapper(List<T> list)
{
- _list.Add(item);
+ _list = list;
}
public int Count => _list.Count;
{
private readonly List<string> _list = new List<string>();
- // For populating test data only. We cannot assume actual input will have this method.
- public void Add(string item)
+ public StringIReadOnlyListWrapper() { }
+
+ public StringIReadOnlyListWrapper(List<string> list)
{
- _list.Add(item);
+ _list = list;
}
public string this[int index] => _list[index];
{
private readonly List<T> _list = new List<T>();
- public void Add(T item)
+ public GenericIReadOnlyListWrapper() { }
+
+ public GenericIReadOnlyListWrapper(List<T> list)
{
- _list.Add(item);
+ _list = list;
}
public T this[int index] => _list[index];
public virtual bool IsReadOnly => ((IDictionary<string, string>)_dictionary).IsReadOnly;
- public void Add(string key, string value)
+ public virtual void Add(string key, string value)
{
((IDictionary<string, string>)_dictionary).Add(key, value);
}
public class StringListWrapper : List<string> { }
+ class MyMyList<T> : GenericListWrapper<T>
+ {
+ }
+
+ class MyListString : GenericListWrapper<string>
+ {
+ }
+
public class GenericListWrapper<T> : List<T> { }
public class StringStackWrapper : Stack<string>
public HashtableWrapper MyHashtableWrapper { get; set; }
public ArrayListWrapper MyArrayListWrapper { get; set; }
public SortedListWrapper MySortedListWrapper { get; set; }
+ public StackWrapper MyStackWrapper { get; set; }
+ public QueueWrapper MyQueueWrapper { get; set; }
public static readonly string s_json =
@"{" +
@"""MyIDictionaryWrapper"" : {""key"" : ""value""}," +
@"""MyHashtableWrapper"" : {""key"" : ""value""}," +
@"""MyArrayListWrapper"" : [""Hello""]," +
- @"""MySortedListWrapper"" : {""key"" : ""value""}" +
+ @"""MySortedListWrapper"" : {""key"" : ""value""}," +
+ @"""MyStackWrapper"" : [""Hello""]," +
+ @"""MyQueueWrapper"" : [""Hello""]" +
@"}";
public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
MyHashtableWrapper = new HashtableWrapper(new List<KeyValuePair<string, object>> { new KeyValuePair<string, object>("key", "value" ) });
MyArrayListWrapper = new ArrayListWrapper() { "Hello" };
MySortedListWrapper = new SortedListWrapper() { { "key", "value" } };
+ MyStackWrapper = new StackWrapper();
+ MyQueueWrapper = new QueueWrapper();
+
+ MyStackWrapper.Push("Hello");
+ MyQueueWrapper.Enqueue("Hello");
}
public void Verify()
Assert.Equal("value", ((JsonElement)MyHashtableWrapper["key"]).GetString());
Assert.Equal("Hello", ((JsonElement)MyArrayListWrapper[0]).GetString());
Assert.Equal("value", ((JsonElement)MySortedListWrapper["key"]).GetString());
+ Assert.Equal("Hello", ((JsonElement)MyStackWrapper.Peek()).GetString());
+ Assert.Equal("Hello", ((JsonElement)MyQueueWrapper.Peek()).GetString());
}
}
Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ReadOnlyStringICollectionWrapper>(@"[""1"", ""2""]"));
Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ReadOnlyStringToStringIDictionaryWrapper>(@"{""Key"":""key"",""Value"":""value""}"));
}
+
+ [Fact]
+ public static void Read_HigherOrderCollectionInheritance_Works()
+ {
+ const string json = "[\"test\"]";
+ Assert.Equal("test", JsonSerializer.Deserialize<string[]>(json)[0]);
+ Assert.Equal("test", JsonSerializer.Deserialize<List<string>>(json).First());
+ Assert.Equal("test", JsonSerializer.Deserialize<StringListWrapper>(json).First());
+ Assert.Equal("test", JsonSerializer.Deserialize<GenericListWrapper<string>>(json).First());
+ Assert.Equal("test", JsonSerializer.Deserialize<MyMyList<string>>(json).First());
+ Assert.Equal("test", JsonSerializer.Deserialize<MyListString>(json).First());
+ }
}
}
}
Assert.Equal(0, count);
- // TODO: use reflection to support types deriving from Stack.
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<StackWrapper>(@"[1,2]"));
+ StackWrapper wrapper = JsonSerializer.Deserialize<StackWrapper>(@"[1,2]");
+ expected = 2;
+
+ foreach (JsonElement i in wrapper)
+ {
+ Assert.Equal(expected--, i.GetInt32());
+ }
}
[Fact]
count++;
}
Assert.Equal(0, count);
+
+ QueueWrapper wrapper = JsonSerializer.Deserialize<QueueWrapper>(@"[1,2]");
+ expected = 1;
- // TODO: use reflection to support types deriving from Queue.
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<QueueWrapper>(@"[1,2]"));
+ foreach (JsonElement i in wrapper)
+ {
+ Assert.Equal(expected++, i.GetInt32());
+ }
}
[Fact]
{
Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<SimpleTestClassWithIEnumerableWrapper>(SimpleTestClassWithIEnumerableWrapper.s_json));
Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<SimpleTestClassWithICollectionWrapper>(SimpleTestClassWithICollectionWrapper.s_json));
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<SimpleTestClassWithStackWrapper>(SimpleTestClassWithStackWrapper.s_json));
- Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<SimpleTestClassWithQueueWrapper>(SimpleTestClassWithQueueWrapper.s_json));
}
}
}
string json = JsonSerializer.Serialize(input);
Assert.Equal("[[1,2],[3,4]]", json);
- GenericIEnumerableWrapper<StringIEnumerableWrapper> input2 = new GenericIEnumerableWrapper<StringIEnumerableWrapper>
+ GenericIEnumerableWrapper<StringIEnumerableWrapper> input2 = new GenericIEnumerableWrapper<StringIEnumerableWrapper>(new List<StringIEnumerableWrapper>
{
- new StringIEnumerableWrapper() { "1", "2" },
- new StringIEnumerableWrapper() { "3", "4" }
- };
+ new StringIEnumerableWrapper(new List<string> { "1", "2" }),
+ new StringIEnumerableWrapper(new List<string> { "3", "4" }),
+ });
json = JsonSerializer.Serialize(input2);
Assert.Equal(@"[[""1"",""2""],[""3"",""4""]]", json);
string json = JsonSerializer.Serialize(input);
Assert.Equal("[[1,2],[3,4]]", json);
- GenericIReadOnlyCollectionWrapper<StringIReadOnlyCollectionWrapper> input2 = new GenericIReadOnlyCollectionWrapper<StringIReadOnlyCollectionWrapper>
+ GenericIReadOnlyCollectionWrapper<StringIReadOnlyCollectionWrapper> input2 = new GenericIReadOnlyCollectionWrapper<StringIReadOnlyCollectionWrapper>(new List<StringIReadOnlyCollectionWrapper>
{
- new StringIReadOnlyCollectionWrapper() { "1", "2" },
- new StringIReadOnlyCollectionWrapper() { "3", "4" }
- };
+ new StringIReadOnlyCollectionWrapper(new List<string> { "1", "2" }),
+ new StringIReadOnlyCollectionWrapper(new List<string> { "3", "4" })
+ });
json = JsonSerializer.Serialize(input2);
Assert.Equal(@"[[""1"",""2""],[""3"",""4""]]", json);
string json = JsonSerializer.Serialize(input);
Assert.Equal("[[1,2],[3,4]]", json);
- GenericIReadOnlyListWrapper<StringIReadOnlyListWrapper> input2 = new GenericIReadOnlyListWrapper<StringIReadOnlyListWrapper>
+ GenericIReadOnlyListWrapper<StringIReadOnlyListWrapper> input2 = new GenericIReadOnlyListWrapper<StringIReadOnlyListWrapper>(new List<StringIReadOnlyListWrapper>
{
- new StringIReadOnlyListWrapper() { "1", "2" },
- new StringIReadOnlyListWrapper() { "3", "4" }
- };
+ new StringIReadOnlyListWrapper(new List<string> { "1", "2" }),
+ new StringIReadOnlyListWrapper(new List<string> { "3", "4" })
+ });
json = JsonSerializer.Serialize(input2);
Assert.Equal(@"[[""1"",""2""],[""3"",""4""]]", json);