From 015d26b35972c55d5387b9bba2d44b734dd3f9c7 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Mon, 2 Sep 2019 11:34:15 -0700 Subject: [PATCH 01/15] Initial cleanup. --- .../DefaultImmutableDictionaryConverter.cs | 19 +++- .../DefaultImmutableEnumerableConverter.cs | 19 +++- .../JsonClassInfo.AddProperty.cs | 75 ++++++++----- .../Serialization/JsonClassInfo.Helpers.cs | 14 +-- .../Text/Json/Serialization/JsonClassInfo.cs | 104 ++++++------------ .../Json/Serialization/JsonPropertyInfo.cs | 31 +----- .../Serialization/JsonPropertyInfoCommon.cs | 35 +----- .../Serialization/JsonSerializerOptions.cs | 1 + 8 files changed, 131 insertions(+), 167 deletions(-) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs index b42dec206cbb..548db5471b74 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs @@ -63,8 +63,23 @@ public override object CreateFromDictionary(ref ReadStack state, IDictionary sou string delegateKey = DefaultImmutableEnumerableConverter.GetDelegateKey(immutableCollectionType, elementType, out _, out _); - JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementType, options); - return propertyInfo.CreateImmutableDictionaryInstance(immutableCollectionType, delegateKey, sourceDictionary, state.JsonPath, options); + return CreateImmutableDictionaryInstance(immutableCollectionType, delegateKey, sourceDictionary, state.JsonPath, options); + } + + // Creates an IEnumerable and populates it with the items in the + // sourceList argument then uses the delegateKey argument to identify the appropriate cached + // CreateRange method to create and return the desired immutable collection type. + public static IDictionary CreateImmutableDictionaryInstance(Type collectionType, string delegateKey, IDictionary sourceDictionary, string jsonPath, JsonSerializerOptions options) + { + IDictionary collection = null; + + if (!options.TryGetCreateRangeDelegate(delegateKey, out ImmutableCollectionCreator creator) || + !creator.CreateImmutableDictionary(sourceDictionary, out collection)) + { + ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, jsonPath); + } + + return collection; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs index 036ab546e2f8..a8c64d7c461d 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs @@ -109,8 +109,23 @@ public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out _, out _); - JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementType, options); - return propertyInfo.CreateImmutableCollectionInstance(immutableCollectionType, delegateKey, sourceList, state.JsonPath, options); + return CreateImmutableCollectionInstance(immutableCollectionType, delegateKey, sourceList, state.JsonPath, options); + } + + // Creates an IEnumerable and populates it with the items in the + // sourceList argument then uses the delegateKey argument to identify the appropriate cached + // CreateRange method to create and return the desired immutable collection type. + public static IEnumerable CreateImmutableCollectionInstance(Type collectionType, string delegateKey, IList sourceList, string jsonPath, JsonSerializerOptions options) + { + IEnumerable collection = null; + + if (!options.TryGetCreateRangeDelegate(delegateKey, out ImmutableCollectionCreator creator) || + !creator.CreateImmutableEnumerable(sourceList, out collection)) + { + ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, jsonPath); + } + + return collection; } public static bool IsImmutableEnumerable(Type type) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs index 71ae6eea7446..d20a8823b5e0 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs @@ -2,7 +2,6 @@ // 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; @@ -11,78 +10,95 @@ namespace System.Text.Json { internal partial class JsonClassInfo { - private void AddPolicyProperty(Type propertyType, JsonSerializerOptions options) + private void AddPolicyProperty(ClassType propertyClassType, Type propertyType, Type implementedCollectionType, JsonConverter converter, 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, + propertyClassType: propertyClassType, + parentClassType: typeof(object), // A dummy type (not used). + propertyType: propertyType, propertyInfo: null, // Not a real property so this is null. - classType: typeof(object), // A dummy type (not used). + implementedCollectionType: implementedCollectionType, + converter: converter, options: options); } - private JsonPropertyInfo AddProperty(Type propertyType, PropertyInfo propertyInfo, Type classType, JsonSerializerOptions options) + private JsonPropertyInfo AddProperty(Type parentClassType, Type propertyType, PropertyInfo propertyInfo, 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); + Type implementedCollectionType = GetImplementedCollectionType(parentClassType, propertyType, propertyInfo, out JsonConverter converter, options); + + ClassType classType = GetClassType(propertyType, implementedCollectionType, options); + + return AddProperty( + propertyClassType: classType, + parentClassType: parentClassType, + propertyType: propertyType, + propertyInfo: propertyInfo, + implementedCollectionType: implementedCollectionType, + converter: converter, + options: options); + } - if (implementedType != propertyType) + private JsonPropertyInfo AddProperty(ClassType propertyClassType, Type parentClassType, Type propertyType, PropertyInfo propertyInfo, Type implementedCollectionType, JsonConverter converter, JsonSerializerOptions options) + { + JsonPropertyInfo jsonInfo; + if (implementedCollectionType != propertyType) { - jsonInfo = CreateProperty(implementedType, implementedType, implementedType, propertyInfo, typeof(object), converter, options); + jsonInfo = CreateProperty(propertyClassType, implementedCollectionType, implementedCollectionType, implementedCollectionType, propertyInfo, typeof(object), converter, options); } else { - jsonInfo = CreateProperty(propertyType, propertyType, propertyType, propertyInfo, classType, converter, options); + jsonInfo = CreateProperty(propertyClassType, propertyType, propertyType, propertyType, propertyInfo, parentClassType, converter, options); } // Convert non-immutable dictionary interfaces to concrete types. - if (IsNativelySupportedCollection(propertyType) && implementedType.IsInterface && jsonInfo.ClassType == ClassType.Dictionary) + if (IsNativelySupportedCollection(propertyType) && implementedCollectionType.IsInterface && jsonInfo.ClassType == ClassType.Dictionary) { JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(jsonInfo.ElementType, options); Type newPropertyType = elementPropertyInfo.GetDictionaryConcreteType(); - if (implementedType != newPropertyType) + if (implementedCollectionType != newPropertyType) { - jsonInfo = CreateProperty(propertyType, newPropertyType, implementedType, propertyInfo, classType, converter, options); + jsonInfo = CreateProperty(propertyClassType, propertyType, newPropertyType, implementedCollectionType, propertyInfo, parentClassType, converter, options); } else { - jsonInfo = CreateProperty(propertyType, implementedType, implementedType, propertyInfo, classType, converter, options); + jsonInfo = CreateProperty(propertyClassType, propertyType, implementedCollectionType, implementedCollectionType, propertyInfo, parentClassType, converter, options); } } else if (jsonInfo.ClassType == ClassType.Enumerable && - !implementedType.IsArray && - ((IsDeserializedByAssigningFromList(implementedType) && IsNativelySupportedCollection(propertyType)) || IsSetInterface(implementedType))) + !implementedCollectionType.IsArray && + ((IsDeserializedByAssigningFromList(implementedCollectionType) && IsNativelySupportedCollection(propertyType)) || IsSetInterface(implementedCollectionType))) { JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(jsonInfo.ElementType, options); // Get a runtime type for the implemented property. e.g. ISet -> HashSet, ICollection -> List // We use the element's JsonPropertyInfo so we can utilize the generic support. - Type newPropertyType = elementPropertyInfo.GetConcreteType(implementedType); - if ((implementedType != newPropertyType) && implementedType.IsAssignableFrom(newPropertyType)) + Type newPropertyType = elementPropertyInfo.GetConcreteType(implementedCollectionType); + if ((implementedCollectionType != newPropertyType) && implementedCollectionType.IsAssignableFrom(newPropertyType)) { - jsonInfo = CreateProperty(propertyType, newPropertyType, implementedType, propertyInfo, classType, converter, options); + jsonInfo = CreateProperty(propertyClassType, propertyType, newPropertyType, implementedCollectionType, propertyInfo, parentClassType, converter, options); } else { - jsonInfo = CreateProperty(propertyType, implementedType, implementedType, propertyInfo, classType, converter, options); + jsonInfo = CreateProperty(propertyClassType, propertyType, implementedCollectionType, implementedCollectionType, propertyInfo, parentClassType, converter, options); } } - else if (propertyType != implementedType) + else if (propertyType != implementedCollectionType) { - jsonInfo = CreateProperty(propertyType, implementedType, implementedType, propertyInfo, classType, converter, options); + jsonInfo = CreateProperty(propertyClassType, propertyType, implementedCollectionType, implementedCollectionType, propertyInfo, parentClassType, converter, options); } return jsonInfo; } internal static JsonPropertyInfo CreateProperty( + ClassType propertyClassType, Type declaredPropertyType, Type runtimePropertyType, Type implementedPropertyType, @@ -91,21 +107,21 @@ internal static JsonPropertyInfo CreateProperty( JsonConverter converter, JsonSerializerOptions options) { - bool hasIgnoreAttribute = (JsonPropertyInfo.GetAttribute(propertyInfo) != null); + bool hasIgnoreAttribute = JsonPropertyInfo.GetAttribute(propertyInfo) != null; if (hasIgnoreAttribute) { return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(propertyInfo, options); } Type collectionElementType = null; - switch (GetClassType(runtimePropertyType, options)) + switch (propertyClassType) { case ClassType.Enumerable: case ClassType.ICollectionConstructible: case ClassType.Dictionary: case ClassType.IDictionaryConstructible: case ClassType.Unknown: - collectionElementType = GetElementType(runtimePropertyType, parentClassType, propertyInfo, options); + collectionElementType = GetElementType(propertyClassType, declaredPropertyType, implementedPropertyType, parentClassType, propertyInfo, options); break; } @@ -184,7 +200,7 @@ internal static JsonPropertyInfo CreateProperty( args: null, culture: null); - jsonInfo.Initialize(parentClassType, declaredPropertyType, runtimePropertyType, implementedPropertyType, propertyInfo, collectionElementType, converter, options); + jsonInfo.Initialize(propertyClassType, parentClassType, declaredPropertyType, runtimePropertyType, implementedPropertyType, propertyInfo, collectionElementType, converter, options); return jsonInfo; } @@ -192,6 +208,7 @@ internal static JsonPropertyInfo CreateProperty( internal JsonPropertyInfo CreateRootObject(JsonSerializerOptions options) { return CreateProperty( + ClassType.Object, declaredPropertyType: Type, runtimePropertyType: Type, implementedPropertyType: Type, @@ -204,7 +221,9 @@ internal JsonPropertyInfo CreateRootObject(JsonSerializerOptions options) internal JsonPropertyInfo CreatePolymorphicProperty(JsonPropertyInfo property, Type runtimePropertyType, JsonSerializerOptions options) { JsonPropertyInfo runtimeProperty = CreateProperty( - property.DeclaredPropertyType, runtimePropertyType, + property.ClassType, + property.DeclaredPropertyType, + runtimePropertyType, property.ImplementedPropertyType, property.PropertyInfo, parentClassType: Type, diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs index 1372cbecc754..7cee21804a58 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs @@ -144,7 +144,7 @@ public static Type GetImplementedCollectionType( { Debug.Assert(queryType != null); - if (!(typeof(IEnumerable).IsAssignableFrom(queryType)) || + if (!typeof(IEnumerable).IsAssignableFrom(queryType) || queryType == typeof(string) || queryType.IsInterface || queryType.IsArray || @@ -237,12 +237,6 @@ 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) { if (type.IsGenericType) @@ -294,6 +288,12 @@ public static bool IsNativelySupportedCollection(Type queryType) return s_nativelySupportedNonGenericCollections.Contains(queryType.FullName); } + public static bool IsGenericDictionary(Type type) + { + return type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IDictionary<,>) || + type.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)); + } + // The following methods were copied verbatim from AspNetCore: // https://github.com/aspnet/AspNetCore/blob/13ae0057fbb11fd84fcee8fca46ebc1b2d7c1e6a/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs. diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs index ec2883474e13..f0393c959fa1 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs @@ -33,6 +33,10 @@ internal sealed partial class JsonClassInfo public ConstructorDelegate CreateConcreteEnumerable { get; private set; } public ConstructorDelegate CreateConcreteDictionary { get; private set; } + public JsonSerializerOptions Options { get; private set; } + + public Type Type { get; private set; } + public ClassType ClassType { get; private set; } public JsonPropertyInfo DataExtensionProperty { get; private set; } @@ -51,26 +55,20 @@ public JsonClassInfo ElementClassInfo { get { - if (_elementClassInfo == null && ElementType != null) + if (_elementClassInfo == null && PolicyProperty?.ElementType != null) { Debug.Assert(ClassType == ClassType.Enumerable || ClassType == ClassType.ICollectionConstructible || ClassType == ClassType.Dictionary || ClassType == ClassType.IDictionaryConstructible); - _elementClassInfo = Options.GetOrAddClass(ElementType); + _elementClassInfo = Options.GetOrAddClass(PolicyProperty.ElementType); } return _elementClassInfo; } } - public Type ElementType { get; set; } - - public JsonSerializerOptions Options { get; private set; } - - public Type Type { get; private set; } - public void UpdateSortedPropertyCache(ref ReadStackFrame frame) { Debug.Assert(frame.PropertyRefCache != null); @@ -111,7 +109,10 @@ public JsonClassInfo(Type type, JsonSerializerOptions options) { Type = type; Options = options; - ClassType = GetClassType(type, options); + + Type implementedCollectionType = GetImplementedCollectionType(typeof(object), type, propertyInfo: null, out JsonConverter converter, options); + + ClassType = GetClassType(type, implementedCollectionType, options); CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); @@ -136,7 +137,7 @@ public JsonClassInfo(Type type, JsonSerializerOptions options) if (propertyInfo.GetMethod?.IsPublic == true || propertyInfo.SetMethod?.IsPublic == true) { - JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options); + JsonPropertyInfo jsonPropertyInfo = AddProperty(type, propertyInfo.PropertyType, propertyInfo, options); Debug.Assert(jsonPropertyInfo != null); // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception. @@ -172,7 +173,7 @@ public JsonClassInfo(Type type, JsonSerializerOptions options) case ClassType.Dictionary: { // Add a single property that maps to the class type so we can have policies applied. - AddPolicyProperty(type, options); + AddPolicyProperty(ClassType, type, implementedCollectionType, converter, options); Type objectType; if (PolicyProperty.DeclaredPropertyType.IsInterface) @@ -190,39 +191,33 @@ public JsonClassInfo(Type type, JsonSerializerOptions options) PolicyProperty.ParentClassType, PolicyProperty.PropertyInfo); }); - - ElementType = GetElementType(type, parentType: null, memberInfo: null, options: options); } break; case ClassType.ICollectionConstructible: { // 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); + AddPolicyProperty(ClassType, type, implementedCollectionType, converter, options); CreateConcreteEnumerable = options.MemberAccessorStrategy.CreateConstructor( - typeof(List<>).MakeGenericType(ElementType)); + typeof(List<>).MakeGenericType(PolicyProperty.ElementType)); } break; case ClassType.IDictionaryConstructible: { // 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); + AddPolicyProperty(ClassType, type, implementedCollectionType, converter, options); CreateConcreteDictionary = options.MemberAccessorStrategy.CreateConstructor( - typeof(Dictionary<,>).MakeGenericType(typeof(string), ElementType)); + typeof(Dictionary<,>).MakeGenericType(typeof(string), PolicyProperty.ElementType)); } break; case ClassType.Value: // Add a single property that maps to the class type so we can have policies applied. - AddPolicyProperty(type, options); + AddPolicyProperty(ClassType, type, implementedCollectionType, converter, options); break; case ClassType.Unknown: // Add a single property that maps to the class type so we can have policies applied. - AddPolicyProperty(type, options); + AddPolicyProperty(ClassType, type, implementedCollectionType, converter, options); PropertyCache = new Dictionary(); break; default: @@ -390,20 +385,6 @@ private static bool TryIsPropertyRefEqual(in PropertyRef propertyRef, ReadOnlySp return false; } - private static bool IsPropertyRefEqual(ref PropertyRef propertyRef, PropertyRef other) - { - if (propertyRef.Key == other.Key) - { - if (propertyRef.Info.Name.Length <= PropertyNameKeyLength || - propertyRef.Info.Name.AsSpan().SequenceEqual(other.Info.Name.AsSpan())) - { - return true; - } - } - - return false; - } - public static ulong GetKey(ReadOnlySpan propertyName) { ulong key; @@ -446,11 +427,8 @@ public static ulong GetKey(ReadOnlySpan propertyName) } // 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) + public static Type GetElementType(ClassType classType, Type propertyType, Type implementedType, Type parentType, MemberInfo memberInfo, JsonSerializerOptions options) { - // We want to handle as the implemented collection type, if applicable. - Type implementedType = GetImplementedCollectionType(parentType, propertyType, propertyInfo: null, out _, options); - if (!typeof(IEnumerable).IsAssignableFrom(implementedType)) { return null; @@ -467,7 +445,6 @@ public static Type GetElementType(Type propertyType, Type parentType, MemberInfo if (implementedType.IsGenericType) { Type[] args = implementedType.GetGenericArguments(); - ClassType classType = GetClassType(implementedType, options); if ((classType == ClassType.Dictionary || classType == ClassType.IDictionaryConstructible) && args.Length >= 2) // It is >= 2 in case there is a IDictionary. @@ -503,72 +480,63 @@ public static Type GetElementType(Type propertyType, Type parentType, MemberInfo throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(propertyType, parentType, memberInfo); } - public static ClassType GetClassType(Type type, JsonSerializerOptions options) + private static ClassType GetClassType(Type declaredType, Type implementedCollectionType, JsonSerializerOptions options) { - Debug.Assert(type != null); + Debug.Assert(declaredType != null); - // We want to handle as the implemented collection type, if applicable. - Type implementedType = GetImplementedCollectionType(typeof(object), type, propertyInfo: null, out _, options); - - if (implementedType.IsGenericType && implementedType.GetGenericTypeDefinition() == typeof(Nullable<>)) + if (implementedCollectionType.IsGenericType && implementedCollectionType.GetGenericTypeDefinition() == typeof(Nullable<>)) { - implementedType = Nullable.GetUnderlyingType(implementedType); + implementedCollectionType = Nullable.GetUnderlyingType(implementedCollectionType); } - if (implementedType == typeof(object)) + if (implementedCollectionType == typeof(object)) { return ClassType.Unknown; } - if (options.HasConverter(implementedType)) + if (options.HasConverter(implementedCollectionType)) { return ClassType.Value; } - if (DefaultImmutableDictionaryConverter.IsImmutableDictionary(implementedType) || - IsDeserializedByConstructingWithIDictionary(implementedType)) + if (DefaultImmutableDictionaryConverter.IsImmutableDictionary(implementedCollectionType) || + IsDeserializedByConstructingWithIDictionary(implementedCollectionType)) { return ClassType.IDictionaryConstructible; } - if (typeof(IDictionary).IsAssignableFrom(implementedType)) + if (typeof(IDictionary).IsAssignableFrom(implementedCollectionType)) { return ClassType.Dictionary; } - if (IsGenericDictionary(implementedType)) + if (IsGenericDictionary(implementedCollectionType)) { - return type.IsInterface + return declaredType.IsInterface ? ClassType.Dictionary // IDictionary<,> we can use a concrete type for that. : ClassType.IDictionaryConstructible; // A type implementing IDictionary<,> but not IDictionary, have to buffer that. } - if (implementedType.IsArray || - DefaultImmutableEnumerableConverter.IsImmutableEnumerable(implementedType) || - IsDeserializedByConstructingWithIList(implementedType)) + if (implementedCollectionType.IsArray || + DefaultImmutableEnumerableConverter.IsImmutableEnumerable(implementedCollectionType) || + IsDeserializedByConstructingWithIList(implementedCollectionType)) { return ClassType.ICollectionConstructible; } - if (typeof(IList).IsAssignableFrom(implementedType)) + if (typeof(IList).IsAssignableFrom(implementedCollectionType)) { return ClassType.Enumerable; } - if (typeof(IEnumerable).IsAssignableFrom(implementedType)) + if (typeof(IEnumerable).IsAssignableFrom(implementedCollectionType)) { - return type.IsInterface + return declaredType.IsInterface ? ClassType.Enumerable // IEnumerable we can use a concrete type for that. : ClassType.ICollectionConstructible; // A type implementing IEnumerable but not IList, have to buffer that. } return ClassType.Object; } - - public static bool IsGenericDictionary(Type type) - { - return type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IDictionary<,>) || - type.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)); - } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs index 46f5bf579dae..cf89da8786a1 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs @@ -53,10 +53,6 @@ public void CopyRuntimeSettingsTo(JsonPropertyInfo other) public abstract IDictionary CreateIDictionaryInstance(Type parentType, IDictionary sourceDictionary, string jsonPath, JsonSerializerOptions options); - public abstract IEnumerable CreateImmutableCollectionInstance(Type collectionType, string delegateKey, IList sourceList, string propertyPath, JsonSerializerOptions options); - - public abstract IDictionary CreateImmutableDictionaryInstance(Type collectionType, string delegateKey, IDictionary sourceDictionary, string propertyPath, JsonSerializerOptions options); - // Create a property that is ignored at run-time. It uses the same type (typeof(sbyte)) to help // prevent issues with unsupported types and helps ensure we don't accidently (de)serialize it. public static JsonPropertyInfo CreateIgnoredPropertyPlaceholder(PropertyInfo propertyInfo, JsonSerializerOptions options) @@ -158,8 +154,7 @@ private void DetermineSerializationCapabilities() { if (RuntimePropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName)) { - DefaultImmutableDictionaryConverter.RegisterImmutableDictionary( - RuntimePropertyType, JsonClassInfo.GetElementType(RuntimePropertyType, ParentClassType, PropertyInfo, Options), Options); + DefaultImmutableDictionaryConverter.RegisterImmutableDictionary(RuntimePropertyType, ElementType, Options); DictionaryConverter = s_jsonImmutableDictionaryConverter; } @@ -176,8 +171,7 @@ private void DetermineSerializationCapabilities() { if (RuntimePropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName)) { - DefaultImmutableEnumerableConverter.RegisterImmutableCollection(RuntimePropertyType, - JsonClassInfo.GetElementType(RuntimePropertyType, ParentClassType, PropertyInfo, Options), Options); + DefaultImmutableEnumerableConverter.RegisterImmutableCollection(RuntimePropertyType, ElementType, Options); EnumerableConverter = s_jsonImmutableEnumerableConverter; } @@ -250,6 +244,7 @@ public virtual void GetPolicies() public bool HasSetter { get; set; } public virtual void Initialize( + ClassType propertyClassType, Type parentClassType, Type declaredPropertyType, Type runtimePropertyType, @@ -259,6 +254,7 @@ public virtual void Initialize( JsonConverter converter, JsonSerializerOptions options) { + ClassType = propertyClassType; ParentClassType = parentClassType; DeclaredPropertyType = declaredPropertyType; RuntimePropertyType = runtimePropertyType; @@ -272,25 +268,6 @@ public virtual void Initialize( if (converter != null) { ConverterBase = converter; - - // 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); } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs index a0a68e4383cd..0607bd06a0e2 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs @@ -22,6 +22,7 @@ internal abstract class JsonPropertyInfoCommon Converter { get; internal set; } public override void Initialize( + ClassType propertyClassType, Type parentClassType, Type declaredPropertyType, Type runtimePropertyType, @@ -31,7 +32,7 @@ public override void Initialize( JsonConverter converter, JsonSerializerOptions options) { - base.Initialize(parentClassType, declaredPropertyType, runtimePropertyType, implementedPropertyType, propertyInfo, elementType, converter, options); + base.Initialize(propertyClassType, 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. @@ -301,37 +302,5 @@ public override IDictionary CreateIDictionaryInstance(Type parentType, IDictiona throw ThrowHelper.ThrowNotSupportedException_DeserializeInstanceConstructorNotFound(parentType, sourceDictionary.GetType()); } } - - // Creates an IEnumerable and populates it with the items in the - // sourceList argument then uses the delegateKey argument to identify the appropriate cached - // CreateRange method to create and return the desired immutable collection type. - public override IEnumerable CreateImmutableCollectionInstance(Type collectionType, string delegateKey, IList sourceList, string jsonPath, JsonSerializerOptions options) - { - IEnumerable collection = null; - - if (!options.TryGetCreateRangeDelegate(delegateKey, out ImmutableCollectionCreator creator) || - !creator.CreateImmutableEnumerable(sourceList, out collection)) - { - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, jsonPath); - } - - return collection; - } - - // Creates an IEnumerable and populates it with the items in the - // sourceList argument then uses the delegateKey argument to identify the appropriate cached - // CreateRange method to create and return the desired immutable collection type. - public override IDictionary CreateImmutableDictionaryInstance(Type collectionType, string delegateKey, IDictionary sourceDictionary, string jsonPath, JsonSerializerOptions options) - { - IDictionary collection = null; - - if (!options.TryGetCreateRangeDelegate(delegateKey, out ImmutableCollectionCreator creator) || - !creator.CreateImmutableDictionary(sourceDictionary, out collection)) - { - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, jsonPath); - } - - return collection; - } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs index ff71f9dc2b1f..3a55796ed926 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs @@ -356,6 +356,7 @@ internal JsonPropertyInfo GetJsonPropertyInfoFromClassInfo(Type objectType, Json if (!_objectJsonProperties.TryGetValue(objectType, out JsonPropertyInfo propertyInfo)) { propertyInfo = JsonClassInfo.CreateProperty( + ClassType.Object, objectType, objectType, objectType, From 37a622d0b60bfb6828ce364901b940061f306e74 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 3 Sep 2019 17:44:54 -0700 Subject: [PATCH 02/15] Refactor work in progress. --- .../DefaultDerivedDictionaryConverter.cs | 2 +- .../DefaultDerivedEnumerableConverter.cs | 2 +- .../Converters/DefaultICollectionConverter.cs | 2 +- .../Converters/DefaultIDictionaryConverter.cs | 2 +- .../JsonClassInfo.AddProperty.cs | 119 +++++--------- .../Serialization/JsonClassInfo.Helpers.cs | 13 +- .../Text/Json/Serialization/JsonClassInfo.cs | 90 ++--------- .../Json/Serialization/JsonPropertyInfo.cs | 150 ++++++------------ .../Serialization/JsonPropertyInfoCommon.cs | 30 +--- .../JsonSerializer.Read.HandleArray.cs | 2 +- .../JsonSerializer.Read.HandleDictionary.cs | 6 +- .../JsonSerializer.Read.HandleNull.cs | 2 +- .../JsonSerializer.Write.HandleObject.cs | 8 +- .../Serialization/JsonSerializerOptions.cs | 22 --- .../Text/Json/Serialization/ReadStackFrame.cs | 4 +- 15 files changed, 125 insertions(+), 329 deletions(-) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs index 90b3cf7957a9..460b9443a5ab 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs @@ -11,7 +11,7 @@ internal sealed class DefaultDerivedDictionaryConverter : JsonDictionaryConverte public override object CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options) { JsonPropertyInfo collectionPropertyInfo = state.Current.JsonPropertyInfo; - JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(collectionPropertyInfo.ElementType, options); + JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(collectionPropertyInfo.CollectionElementType, options); return elementPropertyInfo.CreateDerivedDictionaryInstance(collectionPropertyInfo, sourceDictionary, state.JsonPath, options); } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs index fb6c281e05c0..e91c7ce22937 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs @@ -11,7 +11,7 @@ internal sealed class DefaultDerivedEnumerableConverter : JsonEnumerableConverte public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options) { JsonPropertyInfo collectionPropertyInfo = state.Current.JsonPropertyInfo; - JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(collectionPropertyInfo.ElementType, options); + JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(collectionPropertyInfo.CollectionElementType, options); return elementPropertyInfo.CreateDerivedEnumerableInstance(collectionPropertyInfo, sourceList, state.JsonPath, options); } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs index de8d07c08126..4c839f716dc2 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs @@ -15,7 +15,7 @@ public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList enumerableType = state.Current.JsonPropertyInfo.RuntimePropertyType; else enumerableType = state.Current.JsonPropertyInfo.DeclaredPropertyType; - Type elementType = state.Current.JsonPropertyInfo.ElementType; + Type elementType = state.Current.JsonPropertyInfo.CollectionElementType; JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementType, options); return propertyInfo.CreateIEnumerableInstance(enumerableType, sourceList, state.JsonPath, options); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs index 3a24d750029c..ea57b48aad96 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs @@ -15,7 +15,7 @@ public override object CreateFromDictionary(ref ReadStack state, IDictionary sou dictionaryType = state.Current.JsonPropertyInfo.RuntimePropertyType; else dictionaryType = state.Current.JsonPropertyInfo.DeclaredPropertyType; - Type elementType = state.Current.JsonPropertyInfo.ElementType; + Type elementType = state.Current.JsonPropertyInfo.CollectionElementType; JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementType, options); return propertyInfo.CreateIDictionaryInstance(dictionaryType, sourceDictionary, state.JsonPath, options); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs index d20a8823b5e0..a160809b3f77 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs @@ -2,6 +2,7 @@ // 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; @@ -45,67 +46,6 @@ private JsonPropertyInfo AddProperty(Type parentClassType, Type propertyType, Pr } private JsonPropertyInfo AddProperty(ClassType propertyClassType, Type parentClassType, Type propertyType, PropertyInfo propertyInfo, Type implementedCollectionType, JsonConverter converter, JsonSerializerOptions options) - { - JsonPropertyInfo jsonInfo; - if (implementedCollectionType != propertyType) - { - jsonInfo = CreateProperty(propertyClassType, implementedCollectionType, implementedCollectionType, implementedCollectionType, propertyInfo, typeof(object), converter, options); - } - else - { - jsonInfo = CreateProperty(propertyClassType, propertyType, propertyType, propertyType, propertyInfo, parentClassType, converter, options); - } - - // Convert non-immutable dictionary interfaces to concrete types. - if (IsNativelySupportedCollection(propertyType) && implementedCollectionType.IsInterface && jsonInfo.ClassType == ClassType.Dictionary) - { - JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(jsonInfo.ElementType, options); - - Type newPropertyType = elementPropertyInfo.GetDictionaryConcreteType(); - if (implementedCollectionType != newPropertyType) - { - jsonInfo = CreateProperty(propertyClassType, propertyType, newPropertyType, implementedCollectionType, propertyInfo, parentClassType, converter, options); - } - else - { - jsonInfo = CreateProperty(propertyClassType, propertyType, implementedCollectionType, implementedCollectionType, propertyInfo, parentClassType, converter, options); - } - } - else if (jsonInfo.ClassType == ClassType.Enumerable && - !implementedCollectionType.IsArray && - ((IsDeserializedByAssigningFromList(implementedCollectionType) && IsNativelySupportedCollection(propertyType)) || IsSetInterface(implementedCollectionType))) - { - JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(jsonInfo.ElementType, options); - - // Get a runtime type for the implemented property. e.g. ISet -> HashSet, ICollection -> List - // We use the element's JsonPropertyInfo so we can utilize the generic support. - Type newPropertyType = elementPropertyInfo.GetConcreteType(implementedCollectionType); - if ((implementedCollectionType != newPropertyType) && implementedCollectionType.IsAssignableFrom(newPropertyType)) - { - jsonInfo = CreateProperty(propertyClassType, propertyType, newPropertyType, implementedCollectionType, propertyInfo, parentClassType, converter, options); - } - else - { - jsonInfo = CreateProperty(propertyClassType, propertyType, implementedCollectionType, implementedCollectionType, propertyInfo, parentClassType, converter, options); - } - } - else if (propertyType != implementedCollectionType) - { - jsonInfo = CreateProperty(propertyClassType, propertyType, implementedCollectionType, implementedCollectionType, propertyInfo, parentClassType, converter, options); - } - - return jsonInfo; - } - - internal static JsonPropertyInfo CreateProperty( - ClassType propertyClassType, - Type declaredPropertyType, - Type runtimePropertyType, - Type implementedPropertyType, - PropertyInfo propertyInfo, - Type parentClassType, - JsonConverter converter, - JsonSerializerOptions options) { bool hasIgnoreAttribute = JsonPropertyInfo.GetAttribute(propertyInfo) != null; if (hasIgnoreAttribute) @@ -121,32 +61,45 @@ internal static JsonPropertyInfo CreateProperty( case ClassType.Dictionary: case ClassType.IDictionaryConstructible: case ClassType.Unknown: - collectionElementType = GetElementType(propertyClassType, declaredPropertyType, implementedPropertyType, parentClassType, propertyInfo, options); + collectionElementType = GetElementType(propertyClassType, propertyType, implementedCollectionType, parentClassType, propertyInfo); break; } + return CreateProperty(propertyClassType, propertyType, implementedCollectionType, collectionElementType, propertyInfo, parentClassType, converter, options); + } + + private static JsonPropertyInfo CreateProperty( + ClassType propertyClassType, + Type propertyType, + Type implementedCollectionType, + Type collectionElementType, + PropertyInfo propertyInfo, + Type parentClassType, + JsonConverter converter, + JsonSerializerOptions options) + { // Create the JsonPropertyInfo Type propertyInfoClassType; - if (runtimePropertyType.IsGenericType && runtimePropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) + if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { // First try to find a converter for the Nullable, then if not found use the underlying type. // This supports custom converters that want to (de)serialize as null when the value is not null. if (converter == null) { - converter = options.DetermineConverterForProperty(parentClassType, runtimePropertyType, propertyInfo); + converter = options.DetermineConverterForProperty(parentClassType, propertyType, propertyInfo); } if (converter != null) { propertyInfoClassType = typeof(JsonPropertyInfoNotNullable<,,,>).MakeGenericType( parentClassType, - declaredPropertyType, - runtimePropertyType, - runtimePropertyType); + propertyType, + propertyType, + propertyType); } else { - Type typeToConvert = Nullable.GetUnderlyingType(runtimePropertyType); + Type typeToConvert = Nullable.GetUnderlyingType(propertyType); converter = options.DetermineConverterForProperty(parentClassType, typeToConvert, propertyInfo); propertyInfoClassType = typeof(JsonPropertyInfoNullable<,>).MakeGenericType(parentClassType, typeToConvert); } @@ -155,40 +108,40 @@ internal static JsonPropertyInfo CreateProperty( { if (converter == null) { - converter = options.DetermineConverterForProperty(parentClassType, runtimePropertyType, propertyInfo); + converter = options.DetermineConverterForProperty(parentClassType, propertyType, propertyInfo); } Type typeToConvert = converter?.TypeToConvert; if (typeToConvert == null) { - if (IsNativelySupportedCollection(declaredPropertyType)) + if (IsNativelySupportedCollection(propertyType)) { - typeToConvert = implementedPropertyType; + typeToConvert = implementedCollectionType; } else { - typeToConvert = declaredPropertyType; + typeToConvert = propertyType; } } // For the covariant case, create JsonPropertyInfoNotNullable. The generic constraints are "where TConverter : TDeclaredProperty". - if (runtimePropertyType.IsAssignableFrom(typeToConvert)) + if (propertyType.IsAssignableFrom(typeToConvert)) { propertyInfoClassType = typeof(JsonPropertyInfoNotNullable<,,,>).MakeGenericType( parentClassType, - declaredPropertyType, - runtimePropertyType, + propertyType, + propertyType, typeToConvert); } else { - Debug.Assert(typeToConvert.IsAssignableFrom(runtimePropertyType)); + Debug.Assert(typeToConvert.IsAssignableFrom(propertyType)); // For the contravariant case, create JsonPropertyInfoNotNullableContravariant. The generic constraints are "where TDeclaredProperty : TConverter". propertyInfoClassType = typeof(JsonPropertyInfoNotNullableContravariant<,,,>).MakeGenericType( parentClassType, - declaredPropertyType, - runtimePropertyType, + propertyType, + propertyType, typeToConvert); } } @@ -200,7 +153,7 @@ internal static JsonPropertyInfo CreateProperty( args: null, culture: null); - jsonInfo.Initialize(propertyClassType, parentClassType, declaredPropertyType, runtimePropertyType, implementedPropertyType, propertyInfo, collectionElementType, converter, options); + jsonInfo.Initialize(propertyClassType, parentClassType, propertyType, runtimePropertyType, implementedCollectionType, propertyInfo, collectionElementType, converter, options); return jsonInfo; } @@ -209,9 +162,9 @@ internal JsonPropertyInfo CreateRootObject(JsonSerializerOptions options) { return CreateProperty( ClassType.Object, - declaredPropertyType: Type, - runtimePropertyType: Type, - implementedPropertyType: Type, + propertyType: Type, + implementedCollectionType: Type, + collectionElementType: null, propertyInfo: null, parentClassType: Type, converter: null, @@ -223,8 +176,8 @@ internal JsonPropertyInfo CreatePolymorphicProperty(JsonPropertyInfo property, T JsonPropertyInfo runtimeProperty = CreateProperty( property.ClassType, property.DeclaredPropertyType, - runtimePropertyType, property.ImplementedPropertyType, + property.CollectionElementType, property.PropertyInfo, parentClassType: Type, converter: null, diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs index 7cee21804a58..e1dde9f4dca3 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs @@ -161,12 +161,15 @@ public static Type GetImplementedCollectionType( return queryType; } - Type baseType = queryType.GetTypeInfo().BaseType; - - // Check if the base type is a supported concrete collection. - if (IsNativelySupportedCollection(baseType)) + Type baseType = queryType.BaseType; + while (baseType != null) { - return baseType; + // Check if the base type is a supported concrete collection. + if (IsNativelySupportedCollection(baseType)) + { + return baseType; + } + baseType = baseType.BaseType; } // Try generic interfaces with add methods. diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs index f0393c959fa1..f71aebfd0078 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs @@ -30,8 +30,6 @@ internal sealed partial class JsonClassInfo public delegate object ConstructorDelegate(); public ConstructorDelegate CreateObject { get; private set; } - public ConstructorDelegate CreateConcreteEnumerable { get; private set; } - public ConstructorDelegate CreateConcreteDictionary { get; private set; } public JsonSerializerOptions Options { get; private set; } @@ -41,34 +39,6 @@ internal sealed partial class JsonClassInfo public JsonPropertyInfo DataExtensionProperty { get; private set; } - // If enumerable, the JsonClassInfo for the element type. - private JsonClassInfo _elementClassInfo; - - /// - /// Return the JsonClassInfo for the element type, or null if the type is not an enumerable or dictionary. - /// - /// - /// This should not be called during warm-up (initial creation of JsonClassInfos) to avoid recursive behavior - /// which could result in a StackOverflowException. - /// - public JsonClassInfo ElementClassInfo - { - get - { - if (_elementClassInfo == null && PolicyProperty?.ElementType != null) - { - Debug.Assert(ClassType == ClassType.Enumerable || - ClassType == ClassType.ICollectionConstructible || - ClassType == ClassType.Dictionary || - ClassType == ClassType.IDictionaryConstructible); - - _elementClassInfo = Options.GetOrAddClass(PolicyProperty.ElementType); - } - - return _elementClassInfo; - } - } - public void UpdateSortedPropertyCache(ref ReadStackFrame frame) { Debug.Assert(frame.PropertyRefCache != null); @@ -107,20 +77,19 @@ public void UpdateSortedPropertyCache(ref ReadStackFrame frame) public JsonClassInfo(Type type, JsonSerializerOptions options) { + Type implementedCollectionType = GetImplementedCollectionType(parentClassType: null, type, propertyInfo: null, out JsonConverter converter, options); + Type = type; Options = options; - - Type implementedCollectionType = GetImplementedCollectionType(typeof(object), type, propertyInfo: null, out JsonConverter converter, options); - ClassType = GetClassType(type, implementedCollectionType, options); - CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); - // 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 cache = CreatePropertyCache(properties.Length); @@ -170,46 +139,11 @@ public JsonClassInfo(Type type, JsonSerializerOptions options) } break; case ClassType.Enumerable: - case ClassType.Dictionary: - { - // Add a single property that maps to the class type so we can have policies applied. - AddPolicyProperty(ClassType, type, implementedCollectionType, converter, options); - - Type objectType; - if (PolicyProperty.DeclaredPropertyType.IsInterface) - objectType = PolicyProperty.RuntimePropertyType; - else - objectType = PolicyProperty.DeclaredPropertyType; - - CreateObject = options.MemberAccessorStrategy.CreateConstructor(objectType) - ?? new ConstructorDelegate(() => - { - // Implementing types that don't have default constructors are not supported for deserialization. - // This is implemented as a lambda so we don't blow up valid serialization scenarios. - throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( - PolicyProperty.DeclaredPropertyType, - PolicyProperty.ParentClassType, - PolicyProperty.PropertyInfo); - }); - } - break; case ClassType.ICollectionConstructible: - { - // Add a single property that maps to the class type so we can have policies applied. - AddPolicyProperty(ClassType, type, implementedCollectionType, converter, options); - - CreateConcreteEnumerable = options.MemberAccessorStrategy.CreateConstructor( - typeof(List<>).MakeGenericType(PolicyProperty.ElementType)); - } - break; + case ClassType.Dictionary: case ClassType.IDictionaryConstructible: - { - // Add a single property that maps to the class type so we can have policies applied. - AddPolicyProperty(ClassType, type, implementedCollectionType, converter, options); - - CreateConcreteDictionary = options.MemberAccessorStrategy.CreateConstructor( - typeof(Dictionary<,>).MakeGenericType(typeof(string), PolicyProperty.ElementType)); - } + // Add a single property that maps to the class type so we can have policies applied. + AddPolicyProperty(ClassType, type, implementedCollectionType, converter, options); break; case ClassType.Value: // Add a single property that maps to the class type so we can have policies applied. @@ -231,9 +165,9 @@ private bool DetermineExtensionDataProperty(Dictionary JsonPropertyInfo jsonPropertyInfo = GetPropertyWithUniqueAttribute(typeof(JsonExtensionDataAttribute), cache); if (jsonPropertyInfo != null) { - Type declaredPropertyType = jsonPropertyInfo.DeclaredPropertyType; - if (!typeof(IDictionary).IsAssignableFrom(declaredPropertyType) && - !typeof(IDictionary).IsAssignableFrom(declaredPropertyType)) + Type PropertyType = jsonPropertyInfo.PropertyType; + if (!typeof(IDictionary).IsAssignableFrom(PropertyType) && + !typeof(IDictionary).IsAssignableFrom(PropertyType)) { ThrowHelper.ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(this, jsonPropertyInfo); } @@ -427,7 +361,7 @@ public static ulong GetKey(ReadOnlySpan propertyName) } // Return the element type of the IEnumerable or return null if not an IEnumerable. - public static Type GetElementType(ClassType classType, Type propertyType, Type implementedType, Type parentType, MemberInfo memberInfo, JsonSerializerOptions options) + public static Type GetElementType(ClassType classType, Type propertyType, Type implementedType, Type parentType, MemberInfo memberInfo) { if (!typeof(IEnumerable).IsAssignableFrom(implementedType)) { @@ -480,7 +414,7 @@ public static Type GetElementType(ClassType classType, Type propertyType, Type i throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(propertyType, parentType, memberInfo); } - private static ClassType GetClassType(Type declaredType, Type implementedCollectionType, JsonSerializerOptions options) + public static ClassType GetClassType(Type declaredType, Type implementedCollectionType, JsonSerializerOptions options) { Debug.Assert(declaredType != null); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs index cf89da8786a1..f1cb0b9d28de 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs @@ -24,13 +24,11 @@ internal abstract class JsonPropertyInfo public static readonly JsonPropertyInfo s_missingProperty = new JsonPropertyInfoNotNullable(); - private JsonClassInfo _elementClassInfo; - private JsonClassInfo _runtimeClassInfo; - private JsonClassInfo _declaredTypeClassInfo; + private JsonClassInfo _collectionElementClassInfo; public bool CanBeNull { get; private set; } - public ClassType ClassType; + public ClassType ClassType { get; private set; } public abstract JsonConverter ConverterBase { get; set; } @@ -57,9 +55,11 @@ public void CopyRuntimeSettingsTo(JsonPropertyInfo other) // prevent issues with unsupported types and helps ensure we don't accidently (de)serialize it. public static JsonPropertyInfo CreateIgnoredPropertyPlaceholder(PropertyInfo propertyInfo, JsonSerializerOptions options) { - JsonPropertyInfo jsonPropertyInfo = new JsonPropertyInfoNotNullable(); - jsonPropertyInfo.Options = options; - jsonPropertyInfo.PropertyInfo = propertyInfo; + JsonPropertyInfo jsonPropertyInfo = new JsonPropertyInfoNotNullable + { + Options = options, + PropertyInfo = propertyInfo + }; jsonPropertyInfo.DeterminePropertyName(); Debug.Assert(!jsonPropertyInfo.ShouldDeserialize); @@ -68,9 +68,9 @@ public static JsonPropertyInfo CreateIgnoredPropertyPlaceholder(PropertyInfo pro return jsonPropertyInfo; } - public Type DeclaredPropertyType { get; private set; } + public Type PropertyType { get; private set; } - public Type ImplementedPropertyType { get; private set; } + public Type ImplementedCollectionPropertyType { get; private set; } private void DeterminePropertyName() { @@ -140,50 +140,50 @@ private void DetermineSerializationCapabilities() { ShouldDeserialize = true; - if (RuntimePropertyType.IsArray) + if (PropertyType.IsArray) { // Verify that we don't have a multidimensional array. - if (RuntimePropertyType.GetArrayRank() > 1) + if (PropertyType.GetArrayRank() > 1) { - throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(RuntimePropertyType, ParentClassType, PropertyInfo); + throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(PropertyType, ParentClassType, PropertyInfo); } EnumerableConverter = s_jsonArrayConverter; } else if (ClassType == ClassType.IDictionaryConstructible) { - if (RuntimePropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName)) + if (ImplementedCollectionPropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName)) { - DefaultImmutableDictionaryConverter.RegisterImmutableDictionary(RuntimePropertyType, ElementType, Options); + //DefaultImmutableDictionaryConverter.RegisterImmutableDictionary(ImplementedCollectionPropertyType, ElementType, Options); DictionaryConverter = s_jsonImmutableDictionaryConverter; } - else if (JsonClassInfo.IsDeserializedByConstructingWithIDictionary(RuntimePropertyType)) - { - DictionaryConverter = s_jsonIDictionaryConverter; - } else { - DictionaryConverter = s_jsonDerivedDictionaryConverter; + DictionaryConverter = s_jsonIDictionaryConverter; } } + else if (ClassType == ClassType.Dictionary) + { + DictionaryConverter = s_jsonDerivedDictionaryConverter; + } else if (ClassType == ClassType.ICollectionConstructible) { - if (RuntimePropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName)) + if (ImplementedCollectionPropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName)) { - DefaultImmutableEnumerableConverter.RegisterImmutableCollection(RuntimePropertyType, ElementType, Options); + //DefaultImmutableEnumerableConverter.RegisterImmutableCollection(PropertyType, ElementType, Options); EnumerableConverter = s_jsonImmutableEnumerableConverter; } - else if (JsonClassInfo.IsDeserializedByConstructingWithIList(RuntimePropertyType)) - { - EnumerableConverter = s_jsonICollectionConverter; - } else { - EnumerableConverter = s_jsonDerivedEnumerableConverter; + EnumerableConverter = s_jsonICollectionConverter; } } + else if (ClassType == ClassType.Enumerable) + { + EnumerableConverter = s_jsonDerivedEnumerableConverter; + } } } } @@ -196,25 +196,25 @@ private void DetermineSerializationCapabilities() /// This should not be called during warm-up (initial creation of JsonClassInfos) to avoid recursive behavior /// which could result in a StackOverflowException. /// - public JsonClassInfo ElementClassInfo + public JsonClassInfo CollectionElementClassInfo { get { - if (_elementClassInfo == null && ElementType != null) + if (_collectionElementClassInfo == null && CollectionElementType != null) { Debug.Assert(ClassType == ClassType.Enumerable || ClassType == ClassType.ICollectionConstructible || ClassType == ClassType.Dictionary || ClassType == ClassType.IDictionaryConstructible); - _elementClassInfo = Options.GetOrAddClass(ElementType); + _collectionElementClassInfo = Options.GetOrAddClass(CollectionElementType); } - return _elementClassInfo; + return _collectionElementClassInfo; } } - public Type ElementType { get; set; } + public Type CollectionElementType { get; set; } public JsonEnumerableConverter EnumerableConverter { get; private set; } public JsonDictionaryConverter DictionaryConverter { get; private set; } @@ -227,10 +227,6 @@ public static TAttribute GetAttribute(PropertyInfo propertyInfo) whe return (TAttribute)propertyInfo?.GetCustomAttribute(typeof(TAttribute), inherit: false); } - public abstract Type GetDictionaryConcreteType(); - - public abstract Type GetConcreteType(Type type); - public virtual void GetPolicies() { DetermineSerializationCapabilities(); @@ -246,24 +242,22 @@ public virtual void GetPolicies() public virtual void Initialize( ClassType propertyClassType, Type parentClassType, - Type declaredPropertyType, - Type runtimePropertyType, - Type implementedPropertyType, + Type propertyType, + Type implementedCollectionPropertyType, + Type collectionElementType, PropertyInfo propertyInfo, - Type elementType, JsonConverter converter, JsonSerializerOptions options) { ClassType = propertyClassType; ParentClassType = parentClassType; - DeclaredPropertyType = declaredPropertyType; - RuntimePropertyType = runtimePropertyType; - ImplementedPropertyType = implementedPropertyType; + PropertyType = propertyType; + ImplementedCollectionPropertyType = implementedCollectionPropertyType; + CollectionElementType = collectionElementType; PropertyInfo = propertyInfo; - ElementType = elementType; Options = options; - IsNullableType = runtimePropertyType.IsGenericType && runtimePropertyType.GetGenericTypeDefinition() == typeof(Nullable<>); - CanBeNull = IsNullableType || !runtimePropertyType.IsValueType; + IsNullableType = propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>); + CanBeNull = IsNullableType || !propertyType.IsValueType; if (converter != null) { @@ -304,22 +298,13 @@ public void Read(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReade { Debug.Assert(ShouldDeserialize); - if (ElementClassInfo != null) - { - // Forward the setter to the value-based JsonPropertyInfo. - JsonPropertyInfo propertyInfo = ElementClassInfo.PolicyProperty; - propertyInfo.ReadEnumerable(tokenType, ref state, ref reader); - } - else - { - JsonTokenType originalTokenType = reader.TokenType; - int originalDepth = reader.CurrentDepth; - long originalBytesConsumed = reader.BytesConsumed; + JsonTokenType originalTokenType = reader.TokenType; + int originalDepth = reader.CurrentDepth; + long originalBytesConsumed = reader.BytesConsumed; - OnRead(tokenType, ref state, ref reader); + OnRead(tokenType, ref state, ref reader); - VerifyRead(originalTokenType, originalDepth, originalBytesConsumed, ref state, ref reader); - } + VerifyRead(originalTokenType, originalDepth, originalBytesConsumed, ref state, ref reader); } public void ReadEnumerable(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader) @@ -335,34 +320,6 @@ public void ReadEnumerable(JsonTokenType tokenType, ref ReadStack state, ref Utf VerifyRead(originalTokenType, originalDepth, originalBytesConsumed, ref state, ref reader); } - public JsonClassInfo RuntimeClassInfo - { - get - { - if (_runtimeClassInfo == null) - { - _runtimeClassInfo = Options.GetOrAddClass(RuntimePropertyType); - } - - return _runtimeClassInfo; - } - } - - public JsonClassInfo DeclaredTypeClassInfo - { - get - { - if (_declaredTypeClassInfo == null) - { - _declaredTypeClassInfo = Options.GetOrAddClass(DeclaredPropertyType); - } - - return _declaredTypeClassInfo; - } - } - - public Type RuntimePropertyType { get; private set; } - public abstract void SetValueAsObject(object obj, object value); public bool ShouldSerialize { get; private set; } @@ -418,22 +375,13 @@ public void Write(ref WriteStack state, Utf8JsonWriter writer) { Debug.Assert(ShouldSerialize); - if (state.Current.CollectionEnumerator != null) - { - // Forward the setter to the value-based JsonPropertyInfo. - JsonPropertyInfo propertyInfo = ElementClassInfo.PolicyProperty; - propertyInfo.WriteEnumerable(ref state, writer); - } - else - { - int originalDepth = writer.CurrentDepth; + int originalDepth = writer.CurrentDepth; - OnWrite(ref state.Current, writer); + OnWrite(ref state.Current, writer); - if (originalDepth != writer.CurrentDepth) - { - ThrowHelper.ThrowJsonException_SerializationConverterWrite(state.PropertyPath, ConverterBase.ToString()); - } + if (originalDepth != writer.CurrentDepth) + { + ThrowHelper.ThrowJsonException_SerializationConverterWrite(state.PropertyPath, ConverterBase.ToString()); } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs index 0607bd06a0e2..e7a2ac2afb22 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs @@ -24,21 +24,20 @@ internal abstract class JsonPropertyInfoCommon(); } - public override Type GetDictionaryConcreteType() - { - return typeof(Dictionary); - } - - public override Type GetConcreteType(Type parentType) - { - if (JsonClassInfo.IsDeserializedByAssigningFromList(parentType)) - { - return typeof(List); - } - else if (JsonClassInfo.IsSetInterface(parentType)) - { - return typeof(HashSet); - } - - return parentType; - } - public override IEnumerable CreateDerivedEnumerableInstance(JsonPropertyInfo collectionPropertyInfo, IList sourceList, string jsonPath, JsonSerializerOptions options) { Debug.Assert(collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject != null); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs index e9a1a1e9043b..73da8ea65401 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs @@ -38,7 +38,7 @@ private static void HandleStartArray( // A nested json array so push a new stack frame. if (state.Current.CollectionPropertyInitialized) { - Type elementType = jsonPropertyInfo.ElementClassInfo.Type; + Type elementType = jsonPropertyInfo.CollectionElementClassInfo.Type; state.Push(); state.Current.Initialize(elementType, options); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs index 589d3d77cca8..77831bee9475 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs @@ -26,14 +26,14 @@ private static void HandleStartDictionary(JsonSerializerOptions options, ref Utf if (state.Current.CollectionPropertyInitialized) { state.Push(); - state.Current.JsonClassInfo = jsonPropertyInfo.ElementClassInfo; + state.Current.JsonClassInfo = jsonPropertyInfo.CollectionElementClassInfo; state.Current.InitializeJsonPropertyInfo(); state.Current.CollectionPropertyInitialized = true; ClassType classType = state.Current.JsonClassInfo.ClassType; if (classType == ClassType.Value && - jsonPropertyInfo.ElementClassInfo.Type != typeof(object) && - jsonPropertyInfo.ElementClassInfo.Type != typeof(JsonElement)) + jsonPropertyInfo.CollectionElementClassInfo.Type != typeof(object) && + jsonPropertyInfo.CollectionElementClassInfo.Type != typeof(JsonElement)) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.JsonClassInfo.Type, reader, state.JsonPath); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs index da7f3155d08b..0b01db8f8862 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs @@ -79,7 +79,7 @@ private static bool HandleNull(ref Utf8JsonReader reader, ref ReadStack state) private static void AddNullToCollection(JsonPropertyInfo jsonPropertyInfo, ref Utf8JsonReader reader, ref ReadStack state) { - JsonPropertyInfo elementPropertyInfo = jsonPropertyInfo.ElementClassInfo.PolicyProperty; + JsonPropertyInfo elementPropertyInfo = jsonPropertyInfo.CollectionElementClassInfo.PolicyProperty; // if elementPropertyInfo == null then this element doesn't need a converter (an object). diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs index d14c8caaa451..86bb02ad9d25 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs @@ -113,7 +113,7 @@ private static bool HandleObject( // A property that returns an enumerator keeps the same stack frame. if (jsonPropertyInfo.ClassType == ClassType.Enumerable) { - bool endOfEnumerable = HandleEnumerable(jsonPropertyInfo.ElementClassInfo, options, writer, ref state); + bool endOfEnumerable = HandleEnumerable(jsonPropertyInfo.CollectionElementClassInfo, options, writer, ref state); if (endOfEnumerable) { state.Current.MoveToNextProperty = true; @@ -128,7 +128,7 @@ private static bool HandleObject( { state.Current.IsICollectionConstructibleProperty = true; - bool endOfEnumerable = HandleEnumerable(jsonPropertyInfo.ElementClassInfo, options, writer, ref state); + bool endOfEnumerable = HandleEnumerable(jsonPropertyInfo.CollectionElementClassInfo, options, writer, ref state); if (endOfEnumerable) { state.Current.MoveToNextProperty = true; @@ -140,7 +140,7 @@ private static bool HandleObject( // A property that returns a dictionary keeps the same stack frame. if (jsonPropertyInfo.ClassType == ClassType.Dictionary) { - bool endOfEnumerable = HandleDictionary(jsonPropertyInfo.ElementClassInfo, options, writer, ref state); + bool endOfEnumerable = HandleDictionary(jsonPropertyInfo.CollectionElementClassInfo, options, writer, ref state); if (endOfEnumerable) { state.Current.MoveToNextProperty = true; @@ -155,7 +155,7 @@ private static bool HandleObject( { state.Current.IsIDictionaryConstructibleProperty = true; - bool endOfEnumerable = HandleDictionary(jsonPropertyInfo.ElementClassInfo, options, writer, ref state); + bool endOfEnumerable = HandleDictionary(jsonPropertyInfo.CollectionElementClassInfo, options, writer, ref state); if (endOfEnumerable) { state.Current.MoveToNextProperty = true; diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs index 3a55796ed926..c4f78503c753 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs @@ -2,7 +2,6 @@ // 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; @@ -20,7 +19,6 @@ public sealed partial class JsonSerializerOptions internal static readonly JsonSerializerOptions s_defaultOptions = new JsonSerializerOptions(); private readonly ConcurrentDictionary _classes = new ConcurrentDictionary(); - private readonly ConcurrentDictionary _objectJsonProperties = new ConcurrentDictionary(); private static readonly ConcurrentDictionary s_createRangeDelegates = new ConcurrentDictionary(); private MemberAccessor _memberAccessorStrategy; private JsonNamingPolicy _dictionayKeyPolicy; @@ -351,25 +349,6 @@ internal JsonWriterOptions GetWriterOptions() }; } - internal JsonPropertyInfo GetJsonPropertyInfoFromClassInfo(Type objectType, JsonSerializerOptions options) - { - if (!_objectJsonProperties.TryGetValue(objectType, out JsonPropertyInfo propertyInfo)) - { - propertyInfo = JsonClassInfo.CreateProperty( - ClassType.Object, - 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); @@ -385,7 +364,6 @@ internal bool TryAddCreateRangeDelegate(string key, ImmutableCollectionCreator c return s_createRangeDelegates.TryAdd(key, createRangeDelegate); } - internal void VerifyMutable() { // The default options are hidden and thus should be immutable. diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs index d1d089952942..3a746ea40022 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs @@ -84,7 +84,7 @@ public bool IsProcessingValue() if (CollectionPropertyInitialized) { - classType = JsonPropertyInfo.ElementClassInfo.ClassType; + classType = JsonPropertyInfo.CollectionElementClassInfo.ClassType; } else if (JsonPropertyInfo == null) { @@ -145,7 +145,7 @@ public Type GetElementType() { if (IsCollectionForProperty) { - return JsonPropertyInfo.ElementClassInfo.Type; + return JsonPropertyInfo.CollectionElementClassInfo.Type; } if (IsCollectionForClass) From 0d009a5890bc7cfe687313ed1f00fb6d3150dfda Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Fri, 6 Sep 2019 22:11:04 -0700 Subject: [PATCH 03/15] More work in progress. --- .../Text/Json/Serialization/ClassType.cs | 8 +- .../Converters/DefaultArrayConverter.cs | 31 +- .../DefaultDerivedDictionaryConverter.cs | 97 +++- .../DefaultDerivedEnumerableConverter.cs | 124 ++++- .../Converters/DefaultEnumerableConverter.cs | 92 ---- .../Converters/DefaultICollectionConverter.cs | 90 +++- .../Converters/DefaultIDictionaryConverter.cs | 37 +- .../DefaultImmutableDictionaryConverter.cs | 22 +- .../DefaultImmutableEnumerableConverter.cs | 31 +- .../DefaultIDictionaryConverter.cs | 18 - .../JsonClassInfo.AddProperty.cs | 48 +- .../Serialization/JsonClassInfo.Helpers.cs | 32 -- .../Text/Json/Serialization/JsonClassInfo.cs | 59 +-- .../Serialization/JsonDictionaryConverter.cs | 42 +- .../Serialization/JsonEnumerableConverter.cs | 71 ++- .../Json/Serialization/JsonPropertyInfo.cs | 439 ++++++++++-------- .../Serialization/JsonPropertyInfoCommon.cs | 172 +------ .../JsonPropertyInfoNotNullable.cs | 12 +- ...sonPropertyInfoNotNullableContravariant.cs | 12 +- .../Serialization/JsonPropertyInfoNullable.cs | 4 +- .../JsonSerializer.Read.HandleArray.cs | 223 ++------- .../JsonSerializer.Read.HandleDictionary.cs | 127 +---- .../JsonSerializer.Read.HandleNull.cs | 85 +--- .../JsonSerializer.Read.HandleObject.cs | 52 +-- .../JsonSerializer.Read.HandlePropertyName.cs | 11 +- .../JsonSerializer.Read.HandleValue.cs | 7 +- .../Json/Serialization/JsonSerializer.Read.cs | 8 +- .../JsonSerializer.Write.HandleDictionary.cs | 2 +- .../JsonSerializer.Write.HandleEnumerable.cs | 2 +- .../JsonSerializer.Write.HandleObject.cs | 30 -- .../Serialization/JsonSerializer.Write.cs | 6 +- .../Text/Json/Serialization/ReadStack.cs | 9 +- .../Text/Json/Serialization/ReadStackFrame.cs | 31 +- .../ReflectionEmitMemberAccessor.cs | 3 +- .../Text/Json/Serialization/WriteStack.cs | 14 - .../Json/Serialization/WriteStackFrame.cs | 25 +- .../Text/Json/ThrowHelper.Serialization.cs | 11 +- 37 files changed, 866 insertions(+), 1221 deletions(-) delete mode 100644 src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumerableConverter.cs delete mode 100644 src/System.Text.Json/src/System/Text/Json/Serialization/DefaultIDictionaryConverter.cs diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs index d650b29199f0..d16f13c201de 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs @@ -17,13 +17,7 @@ internal enum ClassType Value = 2, // IEnumerable Enumerable = 3, - // Is deserialized by passing a IList to its constructor - // i.e. immutable collections, readonly collections - ICollectionConstructible = 4, // IDictionary - Dictionary = 5, - // Is deserialized by passing a IDictionary to its constructor - // i.e. immutable dictionaries, readonly dictionaries - IDictionaryConstructible = 6 + Dictionary = 4, } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultArrayConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultArrayConverter.cs index 529d5428ef68..88de5ec3cbd1 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultArrayConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultArrayConverter.cs @@ -3,23 +3,38 @@ // See the LICENSE file in the project root for more information. using System.Collections; +using System.Diagnostics; namespace System.Text.Json.Serialization.Converters { - internal sealed class DefaultArrayConverter : JsonEnumerableConverter + internal sealed class DefaultArrayConverter : JsonTemporaryListConverter { - public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options) + public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) { - Type elementType = state.Current.GetElementType(); + return implementedCollectionType.IsArray; + } + + public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) + { + Debug.Assert(jsonPropertyInfo.DeclaredPropertyType.IsArray); + + return jsonPropertyInfo.DeclaredPropertyType; + } + + public override object EndEnumerable(ref ReadStack state, JsonSerializerOptions options) + { + Debug.Assert(state.Current.EnumerableConverterState?.TemporaryList != null); + + JsonEnumerableConverterState converterState = state.Current.EnumerableConverterState; Array array; - if (sourceList.Count > 0 && sourceList[0] is Array probe) + if (converterState.TemporaryList.Count > 0 && converterState.TemporaryList[0] is Array probe) { - array = Array.CreateInstance(probe.GetType(), sourceList.Count); + array = Array.CreateInstance(probe.GetType(), converterState.TemporaryList.Count); int i = 0; - foreach (IList child in sourceList) + foreach (IList child in converterState.TemporaryList) { if (child is Array childArray) { @@ -29,8 +44,8 @@ public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList } else { - array = Array.CreateInstance(elementType, sourceList.Count); - sourceList.CopyTo(array, 0); + array = Array.CreateInstance(state.Current.JsonPropertyInfo.CollectionElementType, converterState.TemporaryList.Count); + converterState.TemporaryList.CopyTo(array, 0); } return array; diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs index 460b9443a5ab..47e582b6cc30 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs @@ -3,16 +3,105 @@ // See the LICENSE file in the project root for more information. using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; namespace System.Text.Json.Serialization.Converters { internal sealed class DefaultDerivedDictionaryConverter : JsonDictionaryConverter { - public override object CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options) + public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) { - JsonPropertyInfo collectionPropertyInfo = state.Current.JsonPropertyInfo; - JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(collectionPropertyInfo.CollectionElementType, options); - return elementPropertyInfo.CreateDerivedDictionaryInstance(collectionPropertyInfo, sourceDictionary, state.JsonPath, options); + throw new NotImplementedException(); + } + + public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) + { + if (jsonPropertyInfo.DeclaredPropertyType.IsInterface) + { + return typeof(Dictionary<,>).MakeGenericType(typeof(string), jsonPropertyInfo.CollectionElementType); + } + + return jsonPropertyInfo.DeclaredPropertyType; + } + + public override void BeginDictionary(ref ReadStack state, JsonSerializerOptions options) + { + Debug.Assert(state.Current.EnumerableConverterState == null); + + if (state.Current.JsonPropertyInfo.DeclaredPropertyType.IsInterface) + { + state.Current.DictionaryConverterState = new JsonDictionaryConverterState + { + FinalInstance = (IDictionary)state.Current.JsonPropertyInfo.RuntimeClassInfo.CreateObject() + }; + } + else if (state.Current.JsonPropertyInfo.DeclaredPropertyType == state.Current.JsonPropertyInfo.RuntimePropertyType) + { + state.Current.DictionaryConverterState = new JsonDictionaryConverterState + { + FinalInstance = (IDictionary)state.Current.JsonPropertyInfo.DeclaredClassInfo.CreateObject() + }; + } + else + { + state.Current.DictionaryConverterState = new JsonDictionaryConverterState + { + TemporaryDictionary = (IDictionary)state.Current.JsonPropertyInfo.RuntimeClassInfo.CreateObject() + }; + } + } + + public override void AddItemToDictionary(ref ReadStack state, JsonSerializerOptions options, string key, object value) + { + Debug.Assert(state.Current.DictionaryConverterState == null); + + JsonDictionaryConverterState convertState = state.Current.DictionaryConverterState; + + (convertState.FinalInstance ?? convertState.TemporaryDictionary).Add(key, value); + } + + public override object EndDictionary(ref ReadStack state, JsonSerializerOptions options) + { + Debug.Assert(state.Current.DictionaryConverterState != null); + + JsonDictionaryConverterState convertState = state.Current.DictionaryConverterState; + + if (convertState.FinalInstance != null) + return convertState.FinalInstance; + + object instance = state.Current.JsonPropertyInfo.DeclaredClassInfo.CreateObject(); + + if (instance is IDictionary instanceOfIDictionary) + { + if (!instanceOfIDictionary.IsReadOnly) + { + foreach (DictionaryEntry entry in convertState.TemporaryDictionary) + { + instanceOfIDictionary.Add((string)entry.Key, entry.Value); + } + return instanceOfIDictionary; + } + } + /* + else if (instance is IDictionary instanceOfGenericIDictionary) + { + if (!instanceOfGenericIDictionary.IsReadOnly) + { + foreach (DictionaryEntry entry in sourceDictionary) + { + instanceOfGenericIDictionary.Add((string)entry.Key, (TRuntimeProperty)entry.Value); + } + return instanceOfGenericIDictionary; + } + } + */ + + ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection( + state.Current.JsonPropertyInfo.DeclaredPropertyType, + state.Current.JsonPropertyInfo.ParentClassType, + state.Current.JsonPropertyInfo.PropertyInfo); + return null; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs index e91c7ce22937..32bc89de772f 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs @@ -3,16 +3,132 @@ // See the LICENSE file in the project root for more information. using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Reflection; +using System.Linq; namespace System.Text.Json.Serialization.Converters { internal sealed class DefaultDerivedEnumerableConverter : JsonEnumerableConverter { - public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options) + // Cache concrete list constructors for performance. + private static readonly Dictionary s_ctors = new Dictionary(); + + public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) + { + return typeof(IList).IsAssignableFrom(implementedCollectionType) || + (implementedCollectionType.IsGenericType && typeof(ICollection<>).MakeGenericType(collectionElementType).IsAssignableFrom(implementedCollectionType)); + } + + public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) + { + if (jsonPropertyInfo.DeclaredPropertyType.IsInterface) + { + if (jsonPropertyInfo.DeclaredPropertyType.IsGenericType) + { + if (typeof(ISet<>).MakeGenericType(jsonPropertyInfo.CollectionElementType).IsAssignableFrom(jsonPropertyInfo.DeclaredPropertyType)) + return typeof(HashSet<>).MakeGenericType(jsonPropertyInfo.CollectionElementType); + if (typeof(ICollection<>).MakeGenericType(jsonPropertyInfo.CollectionElementType).IsAssignableFrom(jsonPropertyInfo.DeclaredPropertyType)) + return typeof(Collection<>).MakeGenericType(jsonPropertyInfo.CollectionElementType); + } + return typeof(List<>).MakeGenericType(jsonPropertyInfo.CollectionElementType); + } + + return jsonPropertyInfo.DeclaredPropertyType; + } + + public override void BeginEnumerable(ref ReadStack state, JsonSerializerOptions options) + { + Debug.Assert(state.Current.EnumerableConverterState == null); + + JsonClassInfo.ConstructorDelegate ctor = state.Current.JsonPropertyInfo.DeclaredPropertyType.IsInterface + ? FindCachedCtor(state.Current.JsonPropertyInfo.RuntimePropertyType, options) + : state.Current.JsonPropertyInfo.DeclaredClassInfo.CreateObject; + + object instance = ctor(); + + if (instance is IList list) + { + state.Current.EnumerableConverterState = new JsonEnumerableConverterState + { + FinalList = list + }; + } + else + { + //var c = typeof(ImmutableEnumerableCreator<,>).MakeGenericType(typeof(int), typeof(List<>).MakeGenericType(typeof(int))).GetConstructors(); + + //var Test = typeof(JsonEnumerableConverterStateCollection).GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, binder: null, Type.EmptyTypes, modifiers: null); + + //var c = new JsonEnumerableConverterStateCollection(); + + Type collectionType = typeof(JsonEnumerableConverterState.Collection<>).MakeGenericType(state.Current.JsonPropertyInfo.CollectionElementType); + + //var p = Activator.CreateInstance(collectionType); + + JsonEnumerableConverterState.Collection CollectionInstance = (JsonEnumerableConverterState.Collection)FindCachedCtor( + collectionType, + options)(); + CollectionInstance.Instance = instance; + + /*Type CollectionElementType = state.Current.JsonPropertyInfo.CollectionElementType; + + MethodInfo AddMethod = instance + .GetType() + .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .FirstOrDefault(m => + { + ParameterInfo[] Parameters = m.GetParameters(); + return (m.Name == "Add" || m.Name == "System.Collections.Generic.ICollection.Add") && + m.ReturnType == typeof(void) && + Parameters.Length == 1 && + Parameters[0].ParameterType == CollectionElementType; + });*/ + + state.Current.EnumerableConverterState = new JsonEnumerableConverterState + { + FinalCollection = instance, + //CollectionAddAction = (Action)AddMethod.CreateDelegate(typeof(Action)) + }; + } + } + + public override void AddItemToEnumerable(ref ReadStack state, JsonSerializerOptions options, object value) + { + Debug.Assert(state.Current.EnumerableConverterState?.FinalList != null || + state.Current.EnumerableConverterState.FinalCollection != null); + + if (state.Current.EnumerableConverterState.FinalList != null) + { + state.Current.EnumerableConverterState.FinalList.Add(value); + } + else + { + //state.Current.EnumerableConverterState.CollectionAddAction(value); + } + } + + public override object EndEnumerable(ref ReadStack state, JsonSerializerOptions options) + { + Debug.Assert(state.Current.EnumerableConverterState?.FinalList != null || + state.Current.EnumerableConverterState.FinalCollection != null); + + return state.Current.EnumerableConverterState.FinalList ?? state.Current.EnumerableConverterState.FinalCollection; + } + + private JsonClassInfo.ConstructorDelegate FindCachedCtor(Type runtimePropertyType, JsonSerializerOptions options) { - JsonPropertyInfo collectionPropertyInfo = state.Current.JsonPropertyInfo; - JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(collectionPropertyInfo.CollectionElementType, options); - return elementPropertyInfo.CreateDerivedEnumerableInstance(collectionPropertyInfo, sourceList, state.JsonPath, options); + string key = runtimePropertyType.FullName; + + if (!s_ctors.TryGetValue(key, out JsonClassInfo.ConstructorDelegate ctor)) + { + ctor = options.MemberAccessorStrategy.CreateConstructor(runtimePropertyType); + s_ctors[key] = ctor; + } + + return ctor; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumerableConverter.cs deleted file mode 100644 index 5f4635bc9050..000000000000 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumerableConverter.cs +++ /dev/null @@ -1,92 +0,0 @@ -// 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; - -namespace System.Text.Json.Serialization.Converters -{ - internal class JsonEnumerableT : ICollection, IEnumerable, IList, IReadOnlyCollection, IReadOnlyList - { - List _list; - - public JsonEnumerableT(IList sourceList) - { - // TODO: Change sourceList from IList to List so we can do a direct assignment here. - _list = new List(); - - foreach (object item in sourceList) - { - _list.Add((T)item); - } - } - - public T this[int index] { get => (T)_list[index]; set => _list[index] = value; } - - public int Count => _list.Count; - - public bool IsReadOnly => false; - - public void Add(T item) - { - _list.Add(item); - } - - public void Clear() - { - _list.Clear(); - } - - public bool Contains(T item) - { - return _list.Contains(item); - } - - public void CopyTo(T[] array, int arrayIndex) - { - _list.CopyTo(array, arrayIndex); - } - - public IEnumerator GetEnumerator() - { - return _list.GetEnumerator(); - } - - public int IndexOf(T item) - { - return _list.IndexOf(item); - } - - public void Insert(int index, T item) - { - _list.Insert(index, item); - } - - public bool Remove(T item) - { - return _list.Remove(item); - } - - public void RemoveAt(int index) - { - _list.RemoveAt(index); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } - - internal sealed class DefaultEnumerableConverter : JsonEnumerableConverter - { - public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options) - { - Type elementType = state.Current.GetElementType(); - - Type t = typeof(JsonEnumerableT<>).MakeGenericType(elementType); - return (IEnumerable)Activator.CreateInstance(t, sourceList); - } - } -} diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs index 4c839f716dc2..d72547ed256c 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs @@ -3,21 +3,91 @@ // See the LICENSE file in the project root for more information. using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; namespace System.Text.Json.Serialization.Converters { - internal sealed class DefaultICollectionConverter : JsonEnumerableConverter + internal sealed class DefaultICollectionConverter : JsonTemporaryListConverter { - public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options) + public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) { - Type enumerableType; - if (state.Current.JsonPropertyInfo.DeclaredPropertyType.IsInterface) - enumerableType = state.Current.JsonPropertyInfo.RuntimePropertyType; - else - enumerableType = state.Current.JsonPropertyInfo.DeclaredPropertyType; - Type elementType = state.Current.JsonPropertyInfo.CollectionElementType; - JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementType, options); - return propertyInfo.CreateIEnumerableInstance(enumerableType, sourceList, state.JsonPath, options); + //Queues, Stacks, SortedSets, readonly collections + return false; + } + + public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) + { + // Only things you can't spin on should go here. + + // todo: Figure out what the runtime type was before for these collections. + + return typeof(List<>).MakeGenericType(jsonPropertyInfo.CollectionElementType); + } + + public override object EndEnumerable(ref ReadStack state, JsonSerializerOptions options) + { + Debug.Assert(state.Current.EnumerableConverterState?.TemporaryList != null); + + try + { + /* + // Note: Types are defined explicityly here for performance. + if (parentType.IsGenericType) + { + Type genericTypeDefinition = parentType.GetGenericTypeDefinition(); + + IList typedList = (IList)sourceList; + + if (genericTypeDefinition == typeof(Stack<>)) + { + return new Stack(typedList); + } + else if (genericTypeDefinition == typeof(Queue<>)) + { + return new Queue(typedList); + } + else if (genericTypeDefinition == typeof(HashSet<>)) + { + return new HashSet(typedList); + } + else if (genericTypeDefinition == typeof(LinkedList<>)) + { + return new LinkedList(typedList); + } + else if (genericTypeDefinition == typeof(ReadOnlyCollection<>)) + { + return new ReadOnlyCollection(typedList); + } + else if (genericTypeDefinition.FullName == JsonClassInfo.ReadOnlyObservableCollectionGenericTypeName) + { + // new ObservableCollection(typedList) + object ObservableCollection = Activator.CreateInstance( + parentType.Assembly.GetType(JsonClassInfo.ObservableCollectionGenericTypeName).MakeGenericType(typeof(TDeclaredProperty)), + typedList); + + // new ReadOnlyObservableCollection(ObservableCollection); + return (IEnumerable)Activator.CreateInstance(parentType, ObservableCollection); + } + } + else + { + if (parentType == typeof(ArrayList)) + { + return new ArrayList(sourceList); + } + + //Stack & Queue would require a reference to System.Collections.NonGeneric + } + */ + + return (IEnumerable)Activator.CreateInstance(state.Current.JsonPropertyInfo.DeclaredPropertyType, state.Current.EnumerableConverterState.TemporaryList); + } + catch (MissingMethodException) + { + ThrowHelper.ThrowNotSupportedException_DeserializeInstanceConstructorOfTypeNotFound(state.Current.JsonPropertyInfo.DeclaredPropertyType, state.Current.EnumerableConverterState.TemporaryList.GetType()); + return null; + } } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs index ea57b48aad96..275a2cb2f5a8 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs @@ -3,21 +3,38 @@ // See the LICENSE file in the project root for more information. using System.Collections; +using System.Diagnostics; namespace System.Text.Json.Serialization.Converters { - internal sealed class DefaultIDictionaryConverter : JsonDictionaryConverter + internal sealed class DefaultIDictionaryConverter : JsonTemporaryDictionaryConverter { - public override object CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options) + public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) { - Type dictionaryType; - if (state.Current.JsonPropertyInfo.DeclaredPropertyType.IsInterface) - dictionaryType = state.Current.JsonPropertyInfo.RuntimePropertyType; - else - dictionaryType = state.Current.JsonPropertyInfo.DeclaredPropertyType; - Type elementType = state.Current.JsonPropertyInfo.CollectionElementType; - JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementType, options); - return propertyInfo.CreateIDictionaryInstance(dictionaryType, sourceDictionary, state.JsonPath, options); + throw new NotImplementedException(); + } + + public override object EndDictionary(ref ReadStack state, JsonSerializerOptions options) + { + Debug.Assert(state.Current.DictionaryConverterState?.TemporaryDictionary != null); + + // Note: Types are defined explicityly here for performance. + try + { + /*if (parentType.FullName == JsonClassInfo.HashtableTypeName) + { + return new Hashtable(sourceDictionary); + }*/ + + // ReadOnlyDictionary<,> would require a reference to System.ObjectModel + + return (IDictionary)Activator.CreateInstance(state.Current.JsonPropertyInfo.DeclaredPropertyType, state.Current.DictionaryConverterState.TemporaryDictionary); + } + catch (MissingMethodException) + { + ThrowHelper.ThrowNotSupportedException_DeserializeInstanceConstructorOfTypeNotFound(state.Current.JsonPropertyInfo.DeclaredPropertyType, state.Current.DictionaryConverterState.TemporaryDictionary.GetType()); + return null; + } } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs index 548db5471b74..3eeefd94a783 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs @@ -7,7 +7,7 @@ namespace System.Text.Json.Serialization.Converters { - internal sealed class DefaultImmutableDictionaryConverter : JsonDictionaryConverter + internal sealed class DefaultImmutableDictionaryConverter : JsonTemporaryDictionaryConverter { public const string ImmutableDictionaryTypeName = "System.Collections.Immutable.ImmutableDictionary"; public const string ImmutableDictionaryGenericTypeName = "System.Collections.Immutable.ImmutableDictionary`2"; @@ -56,27 +56,39 @@ public static bool IsImmutableDictionary(Type type) } } - public override object CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options) + public override object EndDictionary(ref ReadStack state, JsonSerializerOptions options) { + Debug.Assert(state.Current.DictionaryConverterState?.TemporaryDictionary != null); + Type immutableCollectionType = state.Current.JsonPropertyInfo.RuntimePropertyType; Type elementType = state.Current.GetElementType(); string delegateKey = DefaultImmutableEnumerableConverter.GetDelegateKey(immutableCollectionType, elementType, out _, out _); - return CreateImmutableDictionaryInstance(immutableCollectionType, delegateKey, sourceDictionary, state.JsonPath, options); + return CreateImmutableDictionaryInstance(ref state, immutableCollectionType, delegateKey, state.Current.DictionaryConverterState.TemporaryDictionary, options); + } + + public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) + { + return implementedCollectionType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName); + } + + public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) + { + return jsonPropertyInfo.DeclaredPropertyType; } // Creates an IEnumerable and populates it with the items in the // sourceList argument then uses the delegateKey argument to identify the appropriate cached // CreateRange method to create and return the desired immutable collection type. - public static IDictionary CreateImmutableDictionaryInstance(Type collectionType, string delegateKey, IDictionary sourceDictionary, string jsonPath, JsonSerializerOptions options) + public static IDictionary CreateImmutableDictionaryInstance(ref ReadStack state, Type collectionType, string delegateKey, IDictionary sourceDictionary, JsonSerializerOptions options) { IDictionary collection = null; if (!options.TryGetCreateRangeDelegate(delegateKey, out ImmutableCollectionCreator creator) || !creator.CreateImmutableDictionary(sourceDictionary, out collection)) { - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, jsonPath); + ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, state.JsonPath); } return collection; diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs index a8c64d7c461d..3cebb08eb9fb 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs @@ -3,12 +3,13 @@ // See the LICENSE file in the project root for more information. using System.Collections; +using System.Collections.Generic; using System.Diagnostics; namespace System.Text.Json.Serialization.Converters { // This converter returns enumerables in the System.Collections.Immutable namespace. - internal sealed class DefaultImmutableEnumerableConverter : JsonEnumerableConverter + internal sealed class DefaultImmutableEnumerableConverter : JsonTemporaryListConverter { public const string ImmutableArrayTypeName = "System.Collections.Immutable.ImmutableArray"; public const string ImmutableArrayGenericTypeName = "System.Collections.Immutable.ImmutableArray`1"; @@ -74,7 +75,9 @@ public static string GetDelegateKey( constructingTypeName = DefaultImmutableDictionaryConverter.ImmutableSortedDictionaryTypeName; break; default: - throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(immutableCollectionType, null, null); + ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(immutableCollectionType, null, null); + constructingTypeName = null; + return null; } return $"{constructingTypeName}:{elementType.FullName}"; @@ -102,27 +105,41 @@ public static void RegisterImmutableCollection(Type immutableCollectionType, Typ options.TryAddCreateRangeDelegate(delegateKey, createRangeDelegate); } - public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options) + public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) { + return implementedCollectionType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName); + } + + public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) + { + // todo: Figure out what the runtime type was before for these collections. + + return typeof(List<>).MakeGenericType(jsonPropertyInfo.CollectionElementType); + } + + public override object EndEnumerable(ref ReadStack state, JsonSerializerOptions options) + { + Debug.Assert(state.Current.EnumerableConverterState?.TemporaryList != null); + Type immutableCollectionType = state.Current.JsonPropertyInfo.RuntimePropertyType; - Type elementType = state.Current.GetElementType(); + Type elementType = state.Current.JsonPropertyInfo.CollectionElementType; string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out _, out _); - return CreateImmutableCollectionInstance(immutableCollectionType, delegateKey, sourceList, state.JsonPath, options); + return CreateImmutableCollectionInstance(ref state, immutableCollectionType, delegateKey, state.Current.EnumerableConverterState.TemporaryList, options); } // Creates an IEnumerable and populates it with the items in the // sourceList argument then uses the delegateKey argument to identify the appropriate cached // CreateRange method to create and return the desired immutable collection type. - public static IEnumerable CreateImmutableCollectionInstance(Type collectionType, string delegateKey, IList sourceList, string jsonPath, JsonSerializerOptions options) + public static IEnumerable CreateImmutableCollectionInstance(ref ReadStack state, Type collectionType, string delegateKey, IList sourceList, JsonSerializerOptions options) { IEnumerable collection = null; if (!options.TryGetCreateRangeDelegate(delegateKey, out ImmutableCollectionCreator creator) || !creator.CreateImmutableEnumerable(sourceList, out collection)) { - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, jsonPath); + ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, state.JsonPath); } return collection; diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/DefaultIDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/DefaultIDictionaryConverter.cs deleted file mode 100644 index 720562f5482e..000000000000 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/DefaultIDictionaryConverter.cs +++ /dev/null @@ -1,18 +0,0 @@ -// 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.Text.Json.Serialization.Policies; - -namespace System.Text.Json.Serialization.Converters -{ - internal sealed class DefaultIDictionaryConverter : JsonDictionaryConverter - { - public override IDictionary CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options) - { - Type enumerableType = state.Current.JsonPropertyInfo.RuntimePropertyType; - return (IDictionary)Activator.CreateInstance(enumerableType, sourceDictionary); - } - } -} diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs index a160809b3f77..6e778c981424 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs @@ -57,9 +57,7 @@ private JsonPropertyInfo AddProperty(ClassType propertyClassType, Type parentCla switch (propertyClassType) { case ClassType.Enumerable: - case ClassType.ICollectionConstructible: case ClassType.Dictionary: - case ClassType.IDictionaryConstructible: case ClassType.Unknown: collectionElementType = GetElementType(propertyClassType, propertyType, implementedCollectionType, parentClassType, propertyInfo); break; @@ -70,7 +68,7 @@ private JsonPropertyInfo AddProperty(ClassType propertyClassType, Type parentCla private static JsonPropertyInfo CreateProperty( ClassType propertyClassType, - Type propertyType, + Type declaredPropertyType, Type implementedCollectionType, Type collectionElementType, PropertyInfo propertyInfo, @@ -80,26 +78,25 @@ private static JsonPropertyInfo CreateProperty( { // Create the JsonPropertyInfo Type propertyInfoClassType; - if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) + if (declaredPropertyType.IsGenericType && declaredPropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { // First try to find a converter for the Nullable, then if not found use the underlying type. // This supports custom converters that want to (de)serialize as null when the value is not null. if (converter == null) { - converter = options.DetermineConverterForProperty(parentClassType, propertyType, propertyInfo); + converter = options.DetermineConverterForProperty(parentClassType, declaredPropertyType, propertyInfo); } if (converter != null) { - propertyInfoClassType = typeof(JsonPropertyInfoNotNullable<,,,>).MakeGenericType( + propertyInfoClassType = typeof(JsonPropertyInfoNotNullable<,,>).MakeGenericType( parentClassType, - propertyType, - propertyType, - propertyType); + declaredPropertyType, + declaredPropertyType); } else { - Type typeToConvert = Nullable.GetUnderlyingType(propertyType); + Type typeToConvert = Nullable.GetUnderlyingType(declaredPropertyType); converter = options.DetermineConverterForProperty(parentClassType, typeToConvert, propertyInfo); propertyInfoClassType = typeof(JsonPropertyInfoNullable<,>).MakeGenericType(parentClassType, typeToConvert); } @@ -108,40 +105,38 @@ private static JsonPropertyInfo CreateProperty( { if (converter == null) { - converter = options.DetermineConverterForProperty(parentClassType, propertyType, propertyInfo); + converter = options.DetermineConverterForProperty(parentClassType, declaredPropertyType, propertyInfo); } Type typeToConvert = converter?.TypeToConvert; if (typeToConvert == null) { - if (IsNativelySupportedCollection(propertyType)) + if (IsNativelySupportedCollection(declaredPropertyType)) { typeToConvert = implementedCollectionType; } else { - typeToConvert = propertyType; + typeToConvert = declaredPropertyType; } } // For the covariant case, create JsonPropertyInfoNotNullable. The generic constraints are "where TConverter : TDeclaredProperty". - if (propertyType.IsAssignableFrom(typeToConvert)) + if (declaredPropertyType.IsAssignableFrom(typeToConvert)) { - propertyInfoClassType = typeof(JsonPropertyInfoNotNullable<,,,>).MakeGenericType( + propertyInfoClassType = typeof(JsonPropertyInfoNotNullable<,,>).MakeGenericType( parentClassType, - propertyType, - propertyType, + declaredPropertyType, typeToConvert); } else { - Debug.Assert(typeToConvert.IsAssignableFrom(propertyType)); + Debug.Assert(typeToConvert.IsAssignableFrom(declaredPropertyType)); // For the contravariant case, create JsonPropertyInfoNotNullableContravariant. The generic constraints are "where TDeclaredProperty : TConverter". - propertyInfoClassType = typeof(JsonPropertyInfoNotNullableContravariant<,,,>).MakeGenericType( + propertyInfoClassType = typeof(JsonPropertyInfoNotNullableContravariant<,,>).MakeGenericType( parentClassType, - propertyType, - propertyType, + declaredPropertyType, typeToConvert); } } @@ -153,7 +148,7 @@ private static JsonPropertyInfo CreateProperty( args: null, culture: null); - jsonInfo.Initialize(propertyClassType, parentClassType, propertyType, runtimePropertyType, implementedCollectionType, propertyInfo, collectionElementType, converter, options); + jsonInfo.Initialize(propertyClassType, parentClassType, declaredPropertyType, implementedCollectionType, collectionElementType, propertyInfo, converter, options); return jsonInfo; } @@ -162,7 +157,7 @@ internal JsonPropertyInfo CreateRootObject(JsonSerializerOptions options) { return CreateProperty( ClassType.Object, - propertyType: Type, + declaredPropertyType: Type, implementedCollectionType: Type, collectionElementType: null, propertyInfo: null, @@ -176,12 +171,17 @@ internal JsonPropertyInfo CreatePolymorphicProperty(JsonPropertyInfo property, T JsonPropertyInfo runtimeProperty = CreateProperty( property.ClassType, property.DeclaredPropertyType, - property.ImplementedPropertyType, + property.ImplementedCollectionPropertyType, property.CollectionElementType, property.PropertyInfo, parentClassType: Type, converter: null, options: options); + + Debugger.Launch(); + + runtimeProperty.RuntimePropertyType = runtimePropertyType; + property.CopyRuntimeSettingsTo(runtimeProperty); return runtimeProperty; diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs index f87679182584..5fd9fbfff2ba 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs @@ -206,37 +206,6 @@ public static Type GetImplementedCollectionType( 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: - case HashSetGenericTypeName: - 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<>); @@ -253,7 +222,6 @@ public static bool IsDeserializedByConstructingWithIList(Type type) case StackGenericTypeName: case QueueGenericTypeName: case LinkedListGenericTypeName: - case HashSetGenericTypeName: return true; default: return false; diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs index 44d3455bc063..d8870ba2b641 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs @@ -139,9 +139,7 @@ public JsonClassInfo(Type type, JsonSerializerOptions options) } break; case ClassType.Enumerable: - case ClassType.ICollectionConstructible: case ClassType.Dictionary: - case ClassType.IDictionaryConstructible: // Add a single property that maps to the class type so we can have policies applied. AddPolicyProperty(ClassType, type, implementedCollectionType, converter, options); break; @@ -165,7 +163,7 @@ private bool DetermineExtensionDataProperty(Dictionary JsonPropertyInfo jsonPropertyInfo = GetPropertyWithUniqueAttribute(typeof(JsonExtensionDataAttribute), cache); if (jsonPropertyInfo != null) { - Type PropertyType = jsonPropertyInfo.PropertyType; + Type PropertyType = jsonPropertyInfo.DeclaredPropertyType; if (!typeof(IDictionary).IsAssignableFrom(PropertyType) && !typeof(IDictionary).IsAssignableFrom(PropertyType)) { @@ -380,38 +378,22 @@ public static Type GetElementType(ClassType classType, Type propertyType, Type i { Type[] args = implementedType.GetGenericArguments(); - if ((classType == ClassType.Dictionary || classType == ClassType.IDictionaryConstructible) && - args.Length >= 2) // It is >= 2 in case there is a IDictionary. + if (classType == ClassType.Dictionary && args.Length >= 2) // It is >= 2 in case there is a IDictionary. { if (args[0].UnderlyingSystemType == typeof(string)) return args[1]; - throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(propertyType, parentType, memberInfo); + ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(propertyType, parentType, memberInfo); + return null; } - if ((classType == ClassType.Enumerable || classType == ClassType.ICollectionConstructible) && - args.Length >= 1) // It is >= 1 in case there is an IEnumerable. + if (classType == ClassType.Enumerable && args.Length >= 1) // It is >= 1 in case there is an IEnumerable. { return args[0]; } } - if (implementedType.IsAssignableFrom(typeof(IList)) || - implementedType.IsAssignableFrom(typeof(IDictionary)) || - IsDeserializedByConstructingWithIList(implementedType) || - IsDeserializedByConstructingWithIDictionary(implementedType)) - { - return typeof(object); - } - - // Drive HashTable, SortedList... - if (typeof(IList).IsAssignableFrom(implementedType) || - typeof(IDictionary).IsAssignableFrom(implementedType)) - { - return typeof(object); - } - - throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(propertyType, parentType, memberInfo); + return typeof(object); } public static ClassType GetClassType(Type declaredType, Type implementedCollectionType, JsonSerializerOptions options) @@ -433,41 +415,14 @@ public static ClassType GetClassType(Type declaredType, Type implementedCollecti return ClassType.Value; } - if (DefaultImmutableDictionaryConverter.IsImmutableDictionary(implementedCollectionType) || - IsDeserializedByConstructingWithIDictionary(implementedCollectionType)) - { - return ClassType.IDictionaryConstructible; - } - if (typeof(IDictionary).IsAssignableFrom(implementedCollectionType)) { return ClassType.Dictionary; } - if (IsGenericDictionary(implementedCollectionType)) - { - return declaredType.IsInterface - ? ClassType.Dictionary // IDictionary<,> we can use a concrete type for that. - : ClassType.IDictionaryConstructible; // A type implementing IDictionary<,> but not IDictionary, have to buffer that. - } - - if (implementedCollectionType.IsArray || - DefaultImmutableEnumerableConverter.IsImmutableEnumerable(implementedCollectionType) || - IsDeserializedByConstructingWithIList(implementedCollectionType)) - { - return ClassType.ICollectionConstructible; - } - - if (typeof(IList).IsAssignableFrom(implementedCollectionType)) - { - return ClassType.Enumerable; - } - if (typeof(IEnumerable).IsAssignableFrom(implementedCollectionType)) { - return declaredType.IsInterface - ? ClassType.Enumerable // IEnumerable we can use a concrete type for that. - : ClassType.ICollectionConstructible; // A type implementing IEnumerable but not IList, have to buffer that. + return ClassType.Enumerable; } return ClassType.Object; diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs index 547b840bd092..c1eebaf1e6b1 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs @@ -3,9 +3,44 @@ // See the LICENSE file in the project root for more information. using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; namespace System.Text.Json.Serialization.Converters { + internal class JsonDictionaryConverterState + { + public IDictionary TemporaryDictionary; + public IDictionary FinalInstance; + } + + internal abstract class JsonTemporaryDictionaryConverter : JsonDictionaryConverter + { + public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) + { + // Should runtimetype be something else? + + return typeof(Dictionary<,>).MakeGenericType(typeof(string), jsonPropertyInfo.CollectionElementType); + } + + public override void BeginDictionary(ref ReadStack state, JsonSerializerOptions options) + { + Debug.Assert(state.Current.DictionaryConverterState == null); + + state.Current.DictionaryConverterState = new JsonDictionaryConverterState + { + TemporaryDictionary = (IDictionary)state.Current.JsonPropertyInfo.RuntimeClassInfo.CreateObject() + }; + } + + public override void AddItemToDictionary(ref ReadStack state, JsonSerializerOptions options, string key, object value) + { + Debug.Assert(state.Current.DictionaryConverterState?.TemporaryDictionary != null); + + state.Current.DictionaryConverterState.TemporaryDictionary.Add(key, value); + } + } + // Helper to deserialize data into collections that store key-value pairs (not including KeyValuePair<,>) // e.g. IDictionary, Hashtable, Dictionary<,> IDictionary<,>, SortedList etc. // We'll call these collections "dictionaries". @@ -14,7 +49,10 @@ namespace System.Text.Json.Serialization.Converters // implement KeyValuePair<,>. internal abstract class JsonDictionaryConverter { - // Return type is object, not IDictionary as not all "dictionaries" implement IDictionary e.g. IDictionary. - public abstract object CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options); + public abstract bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType); + public abstract Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo); + public abstract void BeginDictionary(ref ReadStack state, JsonSerializerOptions options); + public abstract void AddItemToDictionary(ref ReadStack state, JsonSerializerOptions options, string key, object value); + public abstract object EndDictionary(ref ReadStack state, JsonSerializerOptions options); } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs index ea69e2699f48..9a05156533ea 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs @@ -3,11 +3,80 @@ // See the LICENSE file in the project root for more information. using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; namespace System.Text.Json.Serialization.Converters { + internal class JsonEnumerableConverterState + { + public abstract class Collection + { + public object Instance; + public abstract void Add(object item); + } + + public sealed class Collection : Collection + { + public override void Add(object item) + { + Debug.Assert(Instance != null && + typeof(ICollection).IsAssignableFrom(Instance.GetType()) && + (item == null || item.GetType() == typeof(T))); + ((ICollection)Instance).Add((T)item); + } + } + + public IList TemporaryList; + public IList FinalList; + public object FinalCollection; + //public Action CollectionAddAction; + } + + internal abstract class JsonTemporaryListConverter : JsonEnumerableConverter + { + // Cache concrete list constructors for performance. + private static readonly Dictionary s_ctors = new Dictionary(); + + public override void BeginEnumerable(ref ReadStack state, JsonSerializerOptions options) + { + Debug.Assert(state.Current.EnumerableConverterState == null); + + state.Current.EnumerableConverterState = new JsonEnumerableConverterState + { + TemporaryList = CreateConcreteList(state.Current.JsonPropertyInfo, options) + }; + } + + public override void AddItemToEnumerable(ref ReadStack state, JsonSerializerOptions options, object value) + { + Debug.Assert(state.Current.EnumerableConverterState?.TemporaryList != null); + + state.Current.EnumerableConverterState.TemporaryList.Add(value); + } + + private IList CreateConcreteList(JsonPropertyInfo jsonPropertyInfo, JsonSerializerOptions options) + { + Debug.Assert(jsonPropertyInfo?.CollectionElementType != null); + + string key = jsonPropertyInfo.CollectionElementType.FullName; + + if (!s_ctors.TryGetValue(key, out JsonClassInfo.ConstructorDelegate ctor)) + { + ctor = options.MemberAccessorStrategy.CreateConstructor(typeof(List<>).MakeGenericType(jsonPropertyInfo.CollectionElementType)); + s_ctors[key] = ctor; + } + + return (IList)ctor(); + } + } + internal abstract class JsonEnumerableConverter { - public abstract IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options); + public abstract bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType); + public abstract Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo); + public abstract void BeginEnumerable(ref ReadStack state, JsonSerializerOptions options); + public abstract void AddItemToEnumerable(ref ReadStack state, JsonSerializerOptions options, object value); + public abstract object EndEnumerable(ref ReadStack state, JsonSerializerOptions options); } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs index f1cb0b9d28de..06db18a6b288 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs @@ -2,7 +2,6 @@ // 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.Diagnostics; using System.Reflection; using System.Text.Json.Serialization; @@ -10,7 +9,7 @@ namespace System.Text.Json { - [DebuggerDisplay("PropertyInfo={PropertyInfo}, Element={ElementClassInfo}")] + [DebuggerDisplay("PropertyInfo={PropertyInfo}, Element={CollectionElementClassInfo}")] internal abstract class JsonPropertyInfo { // Cache the converters so they don't get created for every enumerable property. @@ -22,40 +21,18 @@ internal abstract class JsonPropertyInfo private static readonly JsonDictionaryConverter s_jsonIDictionaryConverter = new DefaultIDictionaryConverter(); private static readonly JsonDictionaryConverter s_jsonImmutableDictionaryConverter = new DefaultImmutableDictionaryConverter(); - public static readonly JsonPropertyInfo s_missingProperty = new JsonPropertyInfoNotNullable(); + public static readonly JsonPropertyInfo s_missingProperty = new JsonPropertyInfoNotNullable(); - private JsonClassInfo _collectionElementClassInfo; - - public bool CanBeNull { get; private set; } - - public ClassType ClassType { get; private set; } - - public abstract JsonConverter ConverterBase { get; set; } - - // Copy any settings defined at run-time to the new property. - public void CopyRuntimeSettingsTo(JsonPropertyInfo other) + public static TAttribute GetAttribute(PropertyInfo propertyInfo) where TAttribute : Attribute { - other.EscapedName = EscapedName; - other.Name = Name; - other.NameAsString = NameAsString; - other.PropertyNameKey = PropertyNameKey; + return (TAttribute)propertyInfo?.GetCustomAttribute(typeof(TAttribute), inherit: false); } - public abstract IList CreateConverterList(); - - public abstract IEnumerable CreateDerivedEnumerableInstance(JsonPropertyInfo collectionPropertyInfo, IList sourceList, string jsonPath, JsonSerializerOptions options); - - public abstract object CreateDerivedDictionaryInstance(JsonPropertyInfo collectionPropertyInfo, IDictionary sourceDictionary, string jsonPath, JsonSerializerOptions options); - - public abstract IEnumerable CreateIEnumerableInstance(Type parentType, IList sourceList, string jsonPath, JsonSerializerOptions options); - - public abstract IDictionary CreateIDictionaryInstance(Type parentType, IDictionary sourceDictionary, string jsonPath, JsonSerializerOptions options); - // Create a property that is ignored at run-time. It uses the same type (typeof(sbyte)) to help // prevent issues with unsupported types and helps ensure we don't accidently (de)serialize it. public static JsonPropertyInfo CreateIgnoredPropertyPlaceholder(PropertyInfo propertyInfo, JsonSerializerOptions options) { - JsonPropertyInfo jsonPropertyInfo = new JsonPropertyInfoNotNullable + JsonPropertyInfo jsonPropertyInfo = new JsonPropertyInfoNotNullable { Options = options, PropertyInfo = propertyInfo @@ -68,127 +45,64 @@ public static JsonPropertyInfo CreateIgnoredPropertyPlaceholder(PropertyInfo pro return jsonPropertyInfo; } - public Type PropertyType { get; private set; } + private JsonClassInfo _collectionElementClassInfo; + private JsonClassInfo _runtimeClassInfo; + private JsonClassInfo _declaredTypeClassInfo; - public Type ImplementedCollectionPropertyType { get; private set; } + public bool CanBeNull { get; private set; } - private void DeterminePropertyName() - { - if (PropertyInfo == null) - { - return; - } + public ClassType ClassType { get; private set; } - JsonPropertyNameAttribute nameAttribute = GetAttribute(PropertyInfo); - if (nameAttribute != null) - { - string name = nameAttribute.Name; - if (name == null) - { - ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameNull(ParentClassType, this); - } + public abstract JsonConverter ConverterBase { get; set; } - NameAsString = name; - } - else if (Options.PropertyNamingPolicy != null) + public Type ParentClassType { get; private set; } + + /// + /// Return the JsonClassInfo for the declared type. + /// + /// + /// This should not be called during warm-up (initial creation of JsonClassInfos) to avoid recursive behavior + /// which could result in a StackOverflowException. + /// + public JsonClassInfo DeclaredClassInfo + { + get { - string name = Options.PropertyNamingPolicy.ConvertName(PropertyInfo.Name); - if (name == null) + if (_declaredTypeClassInfo == null) { - ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameNull(ParentClassType, this); + _declaredTypeClassInfo = Options.GetOrAddClass(DeclaredPropertyType); } - NameAsString = name; - } - else - { - NameAsString = PropertyInfo.Name; + return _declaredTypeClassInfo; } + } - Debug.Assert(NameAsString != null); - - // At this point propertyName is valid UTF16, so just call the simple UTF16->UTF8 encoder. - Name = Encoding.UTF8.GetBytes(NameAsString); - - // Cache the escaped name. - EscapedName = JsonEncodedText.Encode(Name); + public Type DeclaredPropertyType { get; private set; } - ulong key = JsonClassInfo.GetKey(Name); - PropertyNameKey = key; - } + public Type ImplementedCollectionPropertyType { get; private set; } - private void DetermineSerializationCapabilities() + /// + /// Return the JsonClassInfo for the runtime type. + /// + /// + /// This should not be called during warm-up (initial creation of JsonClassInfos) to avoid recursive behavior + /// which could result in a StackOverflowException. + /// + public JsonClassInfo RuntimeClassInfo { - if (ClassType != ClassType.Enumerable && - ClassType != ClassType.ICollectionConstructible && - ClassType != ClassType.Dictionary && - ClassType != ClassType.IDictionaryConstructible) - { - // We serialize if there is a getter + not ignoring readonly properties. - ShouldSerialize = HasGetter && (HasSetter || !Options.IgnoreReadOnlyProperties); - - // We deserialize if there is a setter. - ShouldDeserialize = HasSetter; - } - else + get { - if (HasGetter) + if (_runtimeClassInfo == null) { - ShouldSerialize = true; - - if (HasSetter) - { - ShouldDeserialize = true; - - if (PropertyType.IsArray) - { - // Verify that we don't have a multidimensional array. - if (PropertyType.GetArrayRank() > 1) - { - throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(PropertyType, ParentClassType, PropertyInfo); - } - - EnumerableConverter = s_jsonArrayConverter; - } - else if (ClassType == ClassType.IDictionaryConstructible) - { - if (ImplementedCollectionPropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName)) - { - //DefaultImmutableDictionaryConverter.RegisterImmutableDictionary(ImplementedCollectionPropertyType, ElementType, Options); - - DictionaryConverter = s_jsonImmutableDictionaryConverter; - } - else - { - DictionaryConverter = s_jsonIDictionaryConverter; - } - } - else if (ClassType == ClassType.Dictionary) - { - DictionaryConverter = s_jsonDerivedDictionaryConverter; - } - else if (ClassType == ClassType.ICollectionConstructible) - { - if (ImplementedCollectionPropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName)) - { - //DefaultImmutableEnumerableConverter.RegisterImmutableCollection(PropertyType, ElementType, Options); - - EnumerableConverter = s_jsonImmutableEnumerableConverter; - } - else - { - EnumerableConverter = s_jsonICollectionConverter; - } - } - else if (ClassType == ClassType.Enumerable) - { - EnumerableConverter = s_jsonDerivedEnumerableConverter; - } - } + _runtimeClassInfo = Options.GetOrAddClass(RuntimePropertyType); } + + return _runtimeClassInfo; } } + public Type RuntimePropertyType { get; internal set; } + /// /// Return the JsonClassInfo for the element type, or null if the property is not an enumerable or dictionary. /// @@ -203,9 +117,7 @@ public JsonClassInfo CollectionElementClassInfo if (_collectionElementClassInfo == null && CollectionElementType != null) { Debug.Assert(ClassType == ClassType.Enumerable || - ClassType == ClassType.ICollectionConstructible || - ClassType == ClassType.Dictionary || - ClassType == ClassType.IDictionaryConstructible); + ClassType == ClassType.Dictionary); _collectionElementClassInfo = Options.GetOrAddClass(CollectionElementType); } @@ -214,7 +126,7 @@ public JsonClassInfo CollectionElementClassInfo } } - public Type CollectionElementType { get; set; } + public Type CollectionElementType { get; private set; } public JsonEnumerableConverter EnumerableConverter { get; private set; } public JsonDictionaryConverter DictionaryConverter { get; private set; } @@ -222,67 +134,71 @@ public JsonClassInfo CollectionElementClassInfo // The escaped name passed to the writer. public JsonEncodedText? EscapedName { get; private set; } - public static TAttribute GetAttribute(PropertyInfo propertyInfo) where TAttribute : Attribute - { - return (TAttribute)propertyInfo?.GetCustomAttribute(typeof(TAttribute), inherit: false); - } + public bool HasGetter { get; protected set; } + public bool HasSetter { get; protected set; } - public virtual void GetPolicies() - { - DetermineSerializationCapabilities(); - DeterminePropertyName(); - IgnoreNullValues = Options.IgnoreNullValues; - } + public bool IgnoreNullValues { get; private set; } - public abstract object GetValueAsObject(object obj); + public bool IsNullableType { get; private set; } + + public bool IsPropertyPolicy { get; protected set; } + + // The name from a Json value. This is cached for performance on first deserialize. + public byte[] JsonPropertyName { get; set; } - public bool HasGetter { get; set; } - public bool HasSetter { get; set; } + // The name of the property with any casing policy or the name specified from JsonPropertyNameAttribute. + public byte[] Name { get; private set; } + public string NameAsString { get; private set; } + + // Key for fast property name lookup. + public ulong PropertyNameKey { get; set; } + + // Options can be referenced here since all JsonPropertyInfos originate from a JsonClassInfo that is cached on JsonSerializerOptions. + protected JsonSerializerOptions Options { get; set; } + + public PropertyInfo PropertyInfo { get; private set; } + + public bool ShouldSerialize { get; private set; } + public bool ShouldDeserialize { get; private set; } public virtual void Initialize( ClassType propertyClassType, Type parentClassType, - Type propertyType, + Type declaredPropertyType, Type implementedCollectionPropertyType, - Type collectionElementType, + Type collectionElementType, PropertyInfo propertyInfo, JsonConverter converter, JsonSerializerOptions options) { ClassType = propertyClassType; ParentClassType = parentClassType; - PropertyType = propertyType; + DeclaredPropertyType = declaredPropertyType; ImplementedCollectionPropertyType = implementedCollectionPropertyType; CollectionElementType = collectionElementType; PropertyInfo = propertyInfo; Options = options; - IsNullableType = propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>); - CanBeNull = IsNullableType || !propertyType.IsValueType; + IsNullableType = declaredPropertyType.IsGenericType && declaredPropertyType.GetGenericTypeDefinition() == typeof(Nullable<>); + CanBeNull = IsNullableType || !declaredPropertyType.IsValueType; if (converter != null) { ConverterBase = converter; } - } - - public bool IgnoreNullValues { get; private set; } - - public bool IsNullableType { get; private set; } - - public bool IsPropertyPolicy { get; protected set; } - // The name from a Json value. This is cached for performance on first deserialize. - public byte[] JsonPropertyName { get; set; } - - // The name of the property with any casing policy or the name specified from JsonPropertyNameAttribute. - public byte[] Name { get; private set; } - public string NameAsString { get; private set; } - - // Key for fast property name lookup. - public ulong PropertyNameKey { get; set; } + if (propertyClassType == ClassType.Enumerable || + propertyClassType == ClassType.Dictionary) + { + DetermineEnumerableOrDictionaryConverter(); + } + else + { + RuntimePropertyType = DeclaredPropertyType; + } + } - // Options can be referenced here since all JsonPropertyInfos originate from a JsonClassInfo that is cached on JsonSerializerOptions. - protected JsonSerializerOptions Options { get; set; } + public abstract object GetValueAsObject(object obj); + public abstract void SetValueAsObject(object obj, object value); protected abstract void OnRead(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader); protected abstract void OnReadEnumerable(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader); @@ -290,21 +206,42 @@ public virtual void Initialize( protected virtual void OnWriteDictionary(ref WriteStackFrame current, Utf8JsonWriter writer) { } protected abstract void OnWriteEnumerable(ref WriteStackFrame current, Utf8JsonWriter writer); - public Type ParentClassType { get; private set; } + // Copy any settings defined at run-time to the new property. + public void CopyRuntimeSettingsTo(JsonPropertyInfo other) + { + other.EscapedName = EscapedName; + other.Name = Name; + other.NameAsString = NameAsString; + other.PropertyNameKey = PropertyNameKey; + } - public PropertyInfo PropertyInfo { get; private set; } + public virtual void GetPolicies() + { + DetermineSerializationCapabilities(); + DeterminePropertyName(); + IgnoreNullValues = Options.IgnoreNullValues; + } public void Read(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader) { Debug.Assert(ShouldDeserialize); - JsonTokenType originalTokenType = reader.TokenType; - int originalDepth = reader.CurrentDepth; - long originalBytesConsumed = reader.BytesConsumed; + if (CollectionElementClassInfo != null) + { + // Forward the setter to the value-based JsonPropertyInfo. + JsonPropertyInfo propertyInfo = CollectionElementClassInfo.PolicyProperty; + propertyInfo.ReadEnumerable(tokenType, ref state, ref reader); + } + else + { + JsonTokenType originalTokenType = reader.TokenType; + int originalDepth = reader.CurrentDepth; + long originalBytesConsumed = reader.BytesConsumed; - OnRead(tokenType, ref state, ref reader); + OnRead(tokenType, ref state, ref reader); - VerifyRead(originalTokenType, originalDepth, originalBytesConsumed, ref state, ref reader); + VerifyRead(originalTokenType, originalDepth, originalBytesConsumed, ref state, ref reader); + } } public void ReadEnumerable(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader) @@ -320,11 +257,6 @@ public void ReadEnumerable(JsonTokenType tokenType, ref ReadStack state, ref Utf VerifyRead(originalTokenType, originalDepth, originalBytesConsumed, ref state, ref reader); } - public abstract void SetValueAsObject(object obj, object value); - - public bool ShouldSerialize { get; private set; } - public bool ShouldDeserialize { get; private set; } - private void VerifyRead(JsonTokenType tokenType, int depth, long bytesConsumed, ref ReadStack state, ref Utf8JsonReader reader) { switch (tokenType) @@ -410,5 +342,148 @@ public void WriteEnumerable(ref WriteStack state, Utf8JsonWriter writer) ThrowHelper.ThrowJsonException_SerializationConverterWrite(state.PropertyPath, ConverterBase.ToString()); } } + + private void DeterminePropertyName() + { + if (PropertyInfo == null) + { + return; + } + + JsonPropertyNameAttribute nameAttribute = GetAttribute(PropertyInfo); + if (nameAttribute != null) + { + string name = nameAttribute.Name; + if (name == null) + { + ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameNull(ParentClassType, this); + } + + NameAsString = name; + } + else if (Options.PropertyNamingPolicy != null) + { + string name = Options.PropertyNamingPolicy.ConvertName(PropertyInfo.Name); + if (name == null) + { + ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameNull(ParentClassType, this); + } + + NameAsString = name; + } + else + { + NameAsString = PropertyInfo.Name; + } + + Debug.Assert(NameAsString != null); + + // At this point propertyName is valid UTF16, so just call the simple UTF16->UTF8 encoder. + Name = Encoding.UTF8.GetBytes(NameAsString); + + // Cache the escaped name. + EscapedName = JsonEncodedText.Encode(Name); + + ulong key = JsonClassInfo.GetKey(Name); + PropertyNameKey = key; + } + + private void DetermineSerializationCapabilities() + { + if (ClassType != ClassType.Enumerable && + ClassType != ClassType.Dictionary) + { + // We serialize if there is a getter + not ignoring readonly properties. + ShouldSerialize = HasGetter && (HasSetter || !Options.IgnoreReadOnlyProperties); + + // We deserialize if there is a setter. + ShouldDeserialize = HasSetter; + } + else + { + if (HasGetter) + { + ShouldSerialize = true; + + if (HasSetter) + { + ShouldDeserialize = true; + } + } + } + } + + private void DetermineEnumerableOrDictionaryConverter() + { + if (DeclaredPropertyType.IsArray) + { + // Verify that we don't have a multidimensional array. + if (DeclaredPropertyType.GetArrayRank() > 1) + { + ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(DeclaredPropertyType, ParentClassType, PropertyInfo); + return; + } + + EnumerableConverter = s_jsonArrayConverter; + + RuntimePropertyType = EnumerableConverter.ResolveRunTimeType(this); + } + else if (ClassType == ClassType.Dictionary) + { + if (s_jsonImmutableDictionaryConverter.OwnsImplementedCollectionType(ImplementedCollectionPropertyType, CollectionElementType)) + { + DictionaryConverter = s_jsonImmutableDictionaryConverter; + + RuntimePropertyType = DictionaryConverter.ResolveRunTimeType(this); + + DefaultImmutableDictionaryConverter.RegisterImmutableDictionary(RuntimePropertyType, CollectionElementType, Options); + } + else if (s_jsonIDictionaryConverter.OwnsImplementedCollectionType(ImplementedCollectionPropertyType, CollectionElementType)) + { + DictionaryConverter = s_jsonIDictionaryConverter; + + RuntimePropertyType = DictionaryConverter.ResolveRunTimeType(this); + } + else if (s_jsonDerivedDictionaryConverter.OwnsImplementedCollectionType(ImplementedCollectionPropertyType, CollectionElementType)) + { + DictionaryConverter = s_jsonDerivedDictionaryConverter; + + RuntimePropertyType = DictionaryConverter.ResolveRunTimeType(this); + } + else + { + ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(DeclaredPropertyType, ParentClassType, PropertyInfo); + } + } + else if (ClassType == ClassType.Enumerable) + { + if (s_jsonImmutableEnumerableConverter.OwnsImplementedCollectionType(ImplementedCollectionPropertyType, CollectionElementType)) + { + EnumerableConverter = s_jsonImmutableEnumerableConverter; + + DefaultImmutableEnumerableConverter.RegisterImmutableCollection(RuntimePropertyType, CollectionElementType, Options); + + RuntimePropertyType = EnumerableConverter.ResolveRunTimeType(this); + } + else if (s_jsonICollectionConverter.OwnsImplementedCollectionType(ImplementedCollectionPropertyType, CollectionElementType)) + { + EnumerableConverter = s_jsonICollectionConverter; + + RuntimePropertyType = EnumerableConverter.ResolveRunTimeType(this); + } + else if (s_jsonDerivedEnumerableConverter.OwnsImplementedCollectionType(ImplementedCollectionPropertyType, CollectionElementType)) + { + EnumerableConverter = s_jsonDerivedEnumerableConverter; + + RuntimePropertyType = EnumerableConverter.ResolveRunTimeType(this); + } + else + { + ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection(DeclaredPropertyType, ParentClassType, PropertyInfo); + } + } + else + throw new InvalidOperationException(); + } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs index 6ff0f19f1ab5..81c585cfef5a 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs @@ -2,9 +2,6 @@ // 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.Collections.ObjectModel; using System.Diagnostics; using System.Reflection; using System.Text.Json.Serialization; @@ -14,7 +11,7 @@ namespace System.Text.Json /// /// Represents a strongly-typed property to prevent boxing and to create a direct delegate to the getter\setter. /// - internal abstract class JsonPropertyInfoCommon : JsonPropertyInfo + internal abstract class JsonPropertyInfoCommon : JsonPropertyInfo { public Func Get { get; private set; } public Action Set { get; private set; } @@ -97,172 +94,5 @@ public override void SetValueAsObject(object obj, object value) Set(obj, typedValue); } } - - public override IList CreateConverterList() - { - return new List(); - } - - public override IEnumerable CreateDerivedEnumerableInstance(JsonPropertyInfo collectionPropertyInfo, IList sourceList, string jsonPath, JsonSerializerOptions options) - { - object instance = collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject(); - - if (instance is IList instanceOfIList) - { - if (!instanceOfIList.IsReadOnly) - { - foreach (object item in sourceList) - { - instanceOfIList.Add(item); - } - return instanceOfIList; - } - } - else if (instance is ICollection instanceOfICollection) - { - if (!instanceOfICollection.IsReadOnly) - { - foreach (TRuntimeProperty item in sourceList) - { - instanceOfICollection.Add(item); - } - return instanceOfICollection; - } - } - else if (instance is Stack instanceOfStack) - { - foreach (TRuntimeProperty item in sourceList) - { - instanceOfStack.Push(item); - } - return instanceOfStack; - } - else if (instance is Queue instanceOfQueue) - { - foreach (TRuntimeProperty item in sourceList) - { - instanceOfQueue.Enqueue(item); - } - return instanceOfQueue; - } - - throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( - collectionPropertyInfo.DeclaredPropertyType, - collectionPropertyInfo.ParentClassType, - collectionPropertyInfo.PropertyInfo); - } - - public override object CreateDerivedDictionaryInstance(JsonPropertyInfo collectionPropertyInfo, IDictionary sourceDictionary, string jsonPath, JsonSerializerOptions options) - { - object instance = collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject(); - - if (instance is IDictionary instanceOfIDictionary) - { - if (!instanceOfIDictionary.IsReadOnly) - { - foreach (DictionaryEntry entry in sourceDictionary) - { - instanceOfIDictionary.Add((string)entry.Key, entry.Value); - } - return instanceOfIDictionary; - } - } - else if (instance is IDictionary instanceOfGenericIDictionary) - { - if (!instanceOfGenericIDictionary.IsReadOnly) - { - foreach (DictionaryEntry entry in sourceDictionary) - { - instanceOfGenericIDictionary.Add((string)entry.Key, (TRuntimeProperty)entry.Value); - } - return instanceOfGenericIDictionary; - } - } - - throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( - collectionPropertyInfo.DeclaredPropertyType, - collectionPropertyInfo.ParentClassType, - collectionPropertyInfo.PropertyInfo); - } - - public override IEnumerable CreateIEnumerableInstance(Type parentType, IList sourceList, string jsonPath, JsonSerializerOptions options) - { - // Note: Types are defined explicityly here for performance. - try - { - if (parentType.IsGenericType) - { - Type genericTypeDefinition = parentType.GetGenericTypeDefinition(); - - IList typedList = (IList)sourceList; - - if (genericTypeDefinition == typeof(Stack<>)) - { - return new Stack(typedList); - } - else if (genericTypeDefinition == typeof(Queue<>)) - { - return new Queue(typedList); - } - else if (genericTypeDefinition == typeof(HashSet<>)) - { - return new HashSet(typedList); - } - else if (genericTypeDefinition == typeof(LinkedList<>)) - { - return new LinkedList(typedList); - } - else if (genericTypeDefinition == typeof(ReadOnlyCollection<>)) - { - return new ReadOnlyCollection(typedList); - } - else if (genericTypeDefinition.FullName == JsonClassInfo.ReadOnlyObservableCollectionGenericTypeName) - { - // new ObservableCollection(typedList) - object ObservableCollection = Activator.CreateInstance( - parentType.Assembly.GetType(JsonClassInfo.ObservableCollectionGenericTypeName).MakeGenericType(typeof(TDeclaredProperty)), - typedList); - - // new ReadOnlyObservableCollection(ObservableCollection); - return (IEnumerable)Activator.CreateInstance(parentType, ObservableCollection); - } - } - else - { - if (parentType == typeof(ArrayList)) - { - return new ArrayList(sourceList); - } - - //Stack & Queue would require a reference to System.Collections.NonGeneric - } - - return (IEnumerable)Activator.CreateInstance(parentType, sourceList); - } - catch (MissingMethodException) - { - throw ThrowHelper.ThrowNotSupportedException_DeserializeInstanceConstructorOfTypeNotFound(parentType, sourceList.GetType()); - } - } - - public override IDictionary CreateIDictionaryInstance(Type parentType, IDictionary sourceDictionary, string jsonPath, JsonSerializerOptions options) - { - // Note: Types are defined explicityly here for performance. - try - { - if (parentType.FullName == JsonClassInfo.HashtableTypeName) - { - return new Hashtable(sourceDictionary); - } - - // ReadOnlyDictionary<,> would require a reference to System.ObjectModel - - return (IDictionary)Activator.CreateInstance(parentType, sourceDictionary); - } - catch (MissingMethodException) - { - throw ThrowHelper.ThrowNotSupportedException_DeserializeInstanceConstructorOfTypeNotFound(parentType, sourceDictionary.GetType()); - } - } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs index b81c5ab673d8..d27015c2e336 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs @@ -4,15 +4,14 @@ using System.Collections.Generic; using System.Diagnostics; -using System.Text.Json.Serialization; namespace System.Text.Json { /// /// Represents a strongly-typed property that is not a . /// - internal sealed class JsonPropertyInfoNotNullable : - JsonPropertyInfoCommon + internal sealed class JsonPropertyInfoNotNullable : + JsonPropertyInfoCommon where TConverter : TDeclaredProperty { protected override void OnRead(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader) @@ -41,22 +40,21 @@ protected override void OnReadEnumerable(JsonTokenType tokenType, ref ReadStack ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath); } - if (state.Current.KeyName == null && (state.Current.IsProcessingDictionary || state.Current.IsProcessingIDictionaryConstructible)) + if (state.Current.KeyName == null && state.Current.IsProcessingDictionary) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath); return; } // We need an initialized array in order to store the values. - if ((state.Current.IsProcessingEnumerable || state.Current.IsProcessingICollectionConstructible) && - state.Current.TempEnumerableValues == null && state.Current.ReturnValue == null) + if (state.Current.IsProcessingEnumerable && state.Current.EnumerableConverterState == null) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath); return; } TConverter value = Converter.Read(ref reader, RuntimePropertyType, Options); - JsonSerializer.ApplyValueToEnumerable(ref value, ref state, ref reader); + JsonSerializer.ApplyValueToEnumerable(Options, ref state, ref value); } protected override void OnWrite(ref WriteStackFrame current, Utf8JsonWriter writer) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullableContravariant.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullableContravariant.cs index d939a00f7bf8..b98c740109be 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullableContravariant.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullableContravariant.cs @@ -10,8 +10,8 @@ namespace System.Text.Json.Serialization /// /// Represents a strongly-typed property that is not a . /// - internal sealed class JsonPropertyInfoNotNullableContravariant : - JsonPropertyInfoCommon + internal sealed class JsonPropertyInfoNotNullableContravariant : + JsonPropertyInfoCommon where TDeclaredProperty : TConverter { protected override void OnRead(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader) @@ -42,21 +42,21 @@ protected override void OnReadEnumerable(JsonTokenType tokenType, ref ReadStack ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath); } - if (state.Current.KeyName == null && (state.Current.IsProcessingDictionary || state.Current.IsProcessingIDictionaryConstructible)) + if (state.Current.KeyName == null && state.Current.IsProcessingDictionary) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath); return; } // We need an initialized array in order to store the values. - if (state.Current.IsProcessingEnumerable && state.Current.TempEnumerableValues == null && state.Current.ReturnValue == null) + if (state.Current.IsProcessingEnumerable && state.Current.EnumerableConverterState == null) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath); return; } TConverter value = Converter.Read(ref reader, RuntimePropertyType, Options); - JsonSerializer.ApplyValueToEnumerable(ref value, ref state, ref reader); + JsonSerializer.ApplyValueToEnumerable(Options, ref state, ref value); } protected override void OnWrite(ref WriteStackFrame current, Utf8JsonWriter writer) @@ -68,7 +68,7 @@ protected override void OnWrite(ref WriteStackFrame current, Utf8JsonWriter writ } else { - value = (TConverter)Get(current.CurrentValue); + value = Get(current.CurrentValue); } if (value == null) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs index 06cfd715b74d..10a5a9a339b8 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs @@ -11,7 +11,7 @@ namespace System.Text.Json /// Represents a strongly-typed property that is a . /// internal sealed class JsonPropertyInfoNullable - : JsonPropertyInfoCommon + : JsonPropertyInfoCommon where TProperty : struct { private static readonly Type s_underlyingType = typeof(TProperty); @@ -44,7 +44,7 @@ protected override void OnReadEnumerable(JsonTokenType tokenType, ref ReadStack TProperty value = Converter.Read(ref reader, s_underlyingType, Options); TProperty? nullableValue = new TProperty?(value); - JsonSerializer.ApplyValueToEnumerable(ref nullableValue, ref state, ref reader); + JsonSerializer.ApplyValueToEnumerable(Options, ref state, ref nullableValue); } protected override void OnWrite(ref WriteStackFrame current, Utf8JsonWriter writer) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs index 7d9f9b4ffd5e..11b8d19af092 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs @@ -2,10 +2,7 @@ // 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.Text.Json.Serialization.Converters; namespace System.Text.Json { @@ -13,243 +10,75 @@ public static partial class JsonSerializer { private static void HandleStartArray( JsonSerializerOptions options, - ref Utf8JsonReader reader, ref ReadStack state) { - JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; - if (jsonPropertyInfo == null) - { - jsonPropertyInfo = state.Current.JsonClassInfo.CreateRootObject(options); - } - else if (state.Current.JsonClassInfo.ClassType == ClassType.Unknown) - { - jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, typeof(object), options); - } - - // Verify that we have a valid enumerable. - Type arrayType = jsonPropertyInfo.RuntimePropertyType; - if (!typeof(IEnumerable).IsAssignableFrom(arrayType)) - { - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(arrayType, reader, state.JsonPath); - } - Debug.Assert(state.Current.IsProcessingEnumerableOrDictionary); - // A nested json array so push a new stack frame. + JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; + if (state.Current.CollectionPropertyInitialized) { + // A nested json array so push a new stack frame. Type elementType = jsonPropertyInfo.CollectionElementClassInfo.Type; state.Push(); state.Current.Initialize(elementType, options); - state.Current.CollectionPropertyInitialized = true; - - JsonClassInfo classInfo = state.Current.JsonClassInfo; - - if (state.Current.IsProcessingICollectionConstructible) - { - state.Current.TempEnumerableValues = (IList)classInfo.CreateConcreteEnumerable(); - } - else - { - Debug.Assert(state.Current.IsProcessingEnumerable); - state.Current.ReturnValue = classInfo.CreateObject(); - } - + HandleStartArray(options, ref state); return; } state.Current.CollectionPropertyInitialized = true; - JsonClassInfo collectionClassInfo; - if (jsonPropertyInfo.DeclaredPropertyType == jsonPropertyInfo.ImplementedPropertyType) - { - collectionClassInfo = options.GetOrAddClass(jsonPropertyInfo.RuntimePropertyType); - } - else - { - collectionClassInfo = options.GetOrAddClass(jsonPropertyInfo.DeclaredPropertyType); - } + Debug.Assert(jsonPropertyInfo?.EnumerableConverter != null); - if (jsonPropertyInfo.EnumerableConverter != null) - { - state.Current.TempEnumerableValues = (IList)collectionClassInfo.CreateConcreteEnumerable(); - } - else - { - object value = collectionClassInfo.CreateObject(); - - if (value != null) - { - if (state.Current.ReturnValue != null) - { - state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); - } - else - { - // Primitive arrays being returned without object. - state.Current.SetReturnValue(value); - } - } - } + jsonPropertyInfo.EnumerableConverter.BeginEnumerable(ref state, options); } private static void HandleEndArray( JsonSerializerOptions options, - ref Utf8JsonReader reader, ref ReadStack state) { - if (state.Current.IsEnumerableProperty) - { - // We added the items to the enumberable already. - state.Current.EndProperty(); - } - else if (state.Current.IsICollectionConstructibleProperty) - { - Debug.Assert(state.Current.TempEnumerableValues != null); - JsonEnumerableConverter converter = state.Current.JsonPropertyInfo.EnumerableConverter; - state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, converter.CreateFromList(ref state, state.Current.TempEnumerableValues, options)); - state.Current.EndProperty(); - } - else - { - object value; - if (state.Current.TempEnumerableValues != null) - { - JsonEnumerableConverter converter = state.Current.JsonPropertyInfo.EnumerableConverter; - value = converter.CreateFromList(ref state, state.Current.TempEnumerableValues, options); - } - else - { - value = state.Current.ReturnValue; - } + Debug.Assert(state.Current.JsonPropertyInfo?.EnumerableConverter != null); - if (state.IsLastFrame) - { - // Set the return value directly since this will be returned to the user. - state.Current.Reset(); - state.Current.ReturnValue = value; - } - else - { - state.Pop(); - ApplyObjectToEnumerable(value, ref state, ref reader); - } - } - } + object EnumerableInstance = state.Current.JsonPropertyInfo.EnumerableConverter.EndEnumerable(ref state, options); - // If this method is changed, also change ApplyValueToEnumerable. - internal static void ApplyObjectToEnumerable( - object value, - ref ReadStack state, - ref Utf8JsonReader reader, - bool setPropertyDirectly = false) - { - Debug.Assert(!state.Current.SkipProperty); - - if (state.Current.TempEnumerableValues != null) - { - // Used by nested arrays, arrays with converters, and IsICollectionConstructibles. - state.Current.TempEnumerableValues.Add(value); - } - else if (state.Current.IsEnumerable || (state.Current.IsEnumerableProperty && !setPropertyDirectly)) - { - Debug.Assert(state.Current.ReturnValue != null); - - IList list = (IList)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue); - list.Add(value); - } - else if (state.Current.IsICollectionConstructible || (state.Current.IsICollectionConstructibleProperty && !setPropertyDirectly)) - { - // If we didn't fall into the TempEnumerableValues block above, we have an invalid array. - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(value.GetType(), reader, state.JsonPath); - } - else if (state.Current.IsDictionary || (state.Current.IsDictionaryProperty && !setPropertyDirectly)) - { - Debug.Assert(state.Current.ReturnValue != null); - IDictionary dictionary = (IDictionary)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue); + state.Current.EndProperty(); - string key = state.Current.KeyName; - Debug.Assert(!string.IsNullOrEmpty(key)); - dictionary[key] = value; - } - else if (state.Current.IsIDictionaryConstructible || - (state.Current.IsIDictionaryConstructibleProperty && !setPropertyDirectly)) + if (state.IsLastFrame) { - Debug.Assert(state.Current.TempDictionaryValues != null); - IDictionary dictionary = state.Current.TempDictionaryValues; - - string key = state.Current.KeyName; - Debug.Assert(!string.IsNullOrEmpty(key)); - dictionary[key] = value; + // Set the return value directly since this will be returned to the user. + state.Current.Reset(); + state.Current.ReturnValue = EnumerableInstance; } else { - Debug.Assert(state.Current.JsonPropertyInfo != null); - state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); + state.Pop(); + + if (state.Current.JsonPropertyInfo.EnumerableConverter != null) + state.Current.JsonPropertyInfo.EnumerableConverter.AddItemToEnumerable(ref state, options, EnumerableInstance); + else + state.Current.ReturnValue = EnumerableInstance; } } - // If this method is changed, also change ApplyObjectToEnumerable. internal static void ApplyValueToEnumerable( - ref TProperty value, + JsonSerializerOptions options, ref ReadStack state, - ref Utf8JsonReader reader) + ref TProperty value) { - Debug.Assert(!state.Current.SkipProperty); - - if (state.Current.TempEnumerableValues != null) - { - // Used by nested arrays, arrays with converters, and IsICollectionConstructibles. - state.Current.TempEnumerableValues.Add(value); - } - else if (state.Current.IsProcessingEnumerable) - { - Debug.Assert(state.Current.ReturnValue != null); - - if (state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue) is ICollection genericCollection) - { - genericCollection.Add(value); - return; - } + Debug.Assert(state.Current.JsonPropertyInfo != null); + Debug.Assert(state.Current.IsProcessingEnumerableOrDictionary); - IList list = (IList)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue); - list.Add(value); - } - else if (state.Current.IsProcessingICollectionConstructible) + if (state.Current.IsProcessingEnumerable) { - // If we didn't fall into the TempEnumerableValues block above, we have an invalid array. - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(value.GetType(), reader, state.JsonPath); + state.Current.JsonPropertyInfo.EnumerableConverter.AddItemToEnumerable(ref state, options, value); } else if (state.Current.IsProcessingDictionary) { - Debug.Assert(state.Current.ReturnValue != null); - string key = state.Current.KeyName; Debug.Assert(!string.IsNullOrEmpty(key)); - if (state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue) is IDictionary genericDictionary) - { - genericDictionary[key] = value; - return; - } - - IDictionary dictionary = (IDictionary)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue); - dictionary.Add(key, value); - } - else if (state.Current.IsProcessingIDictionaryConstructible) - { - 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; - } - else - { - Debug.Assert(state.Current.JsonPropertyInfo != null); - state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); + state.Current.JsonPropertyInfo.DictionaryConverter.AddItemToDictionary(ref state, options, key, value); } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs index 49da6251074d..a727059248d1 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs @@ -12,135 +12,12 @@ public static partial class JsonSerializer { private static void HandleStartDictionary(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) { - Debug.Assert(!state.Current.IsProcessingEnumerable); - - JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; - if (jsonPropertyInfo == null) - { - jsonPropertyInfo = state.Current.JsonClassInfo.CreateRootObject(options); - } - - Debug.Assert(jsonPropertyInfo != null); - - // A nested object or dictionary so push new frame. - if (state.Current.CollectionPropertyInitialized) - { - state.Push(); - state.Current.JsonClassInfo = jsonPropertyInfo.CollectionElementClassInfo; - state.Current.InitializeJsonPropertyInfo(); - state.Current.CollectionPropertyInitialized = true; - - ClassType classType = state.Current.JsonClassInfo.ClassType; - if (classType == ClassType.Value && - jsonPropertyInfo.CollectionElementClassInfo.Type != typeof(object) && - jsonPropertyInfo.CollectionElementClassInfo.Type != typeof(JsonElement)) - { - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.JsonClassInfo.Type, reader, state.JsonPath); - } - - JsonClassInfo classInfo = state.Current.JsonClassInfo; - - if (state.Current.IsProcessingIDictionaryConstructible) - { - state.Current.TempDictionaryValues = (IDictionary)classInfo.CreateConcreteDictionary(); - } - else - { - if (!state.Current.IsProcessingDictionary && classInfo.ClassType != ClassType.Object) - { - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(classInfo.Type, reader, state.JsonPath); - return; - } - state.Current.ReturnValue = classInfo.CreateObject(); - } - - return; - } - - state.Current.CollectionPropertyInitialized = true; - - JsonClassInfo dictionaryClassInfo; - if (jsonPropertyInfo.DeclaredPropertyType == jsonPropertyInfo.ImplementedPropertyType) - { - dictionaryClassInfo = options.GetOrAddClass(jsonPropertyInfo.RuntimePropertyType); - } - else - { - dictionaryClassInfo = options.GetOrAddClass(jsonPropertyInfo.DeclaredPropertyType); - } - - if (state.Current.IsProcessingIDictionaryConstructible) - { - state.Current.TempDictionaryValues = (IDictionary)dictionaryClassInfo.CreateConcreteDictionary(); - } - else - { - 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); - } - } - } + throw new NotImplementedException(); } private static void HandleEndDictionary(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) { - if (state.Current.IsDictionaryProperty) - { - // 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 reader, ref state); - } - else - { - // We added the items to the dictionary already. - state.Current.EndProperty(); - } - } - else if (state.Current.IsIDictionaryConstructibleProperty) - { - 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; - if (state.Current.TempDictionaryValues != null) - { - JsonDictionaryConverter converter = state.Current.JsonPropertyInfo.DictionaryConverter; - value = converter.CreateFromDictionary(ref state, state.Current.TempDictionaryValues, options); - } - else - { - value = state.Current.ReturnValue; - } - - if (state.IsLastFrame) - { - // Set the return value directly since this will be returned to the user. - state.Current.Reset(); - state.Current.ReturnValue = value; - } - else - { - state.Pop(); - ApplyObjectToEnumerable(value, ref state, ref reader); - } - } + throw new NotImplementedException(); } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs index 0b01db8f8862..3f55e16550c7 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs @@ -10,90 +10,7 @@ public static partial class JsonSerializer { private static bool HandleNull(ref Utf8JsonReader reader, ref ReadStack state) { - if (state.Current.SkipProperty) - { - // Clear the current property in case it is a dictionary, since dictionaries must have EndProperty() called when completed. - // A non-dictionary property can also have EndProperty() called when completed, although it is redundant. - state.Current.EndProperty(); - - return false; - } - - JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; - - if (jsonPropertyInfo == null || (reader.CurrentDepth == 0 && jsonPropertyInfo.CanBeNull)) - { - Debug.Assert(state.IsLastFrame); - Debug.Assert(state.Current.ReturnValue == null); - return true; - } - - Debug.Assert(jsonPropertyInfo != null); - - if (state.Current.IsCollectionForClass) - { - AddNullToCollection(jsonPropertyInfo, ref reader, ref state); - return false; - } - - if (state.Current.IsCollectionForProperty) - { - if (state.Current.CollectionPropertyInitialized) - { - // Add the element. - AddNullToCollection(jsonPropertyInfo, ref reader, ref state); - } - else - { - // Set the property to null. - ApplyObjectToEnumerable(null, ref state, ref reader, setPropertyDirectly: true); - - // Reset so that `Is*Property` no longer returns true - state.Current.EndProperty(); - } - - return false; - } - - if (!jsonPropertyInfo.CanBeNull) - { - // Allow a value type converter to return a null value representation, such as JsonElement. - // Most likely this will throw JsonException. - jsonPropertyInfo.Read(JsonTokenType.Null, ref state, ref reader); - return false; - } - - if (state.Current.ReturnValue == null) - { - Debug.Assert(state.IsLastFrame); - return true; - } - - if (!jsonPropertyInfo.IgnoreNullValues) - { - state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value: null); - } - - return false; - } - - private static void AddNullToCollection(JsonPropertyInfo jsonPropertyInfo, ref Utf8JsonReader reader, ref ReadStack state) - { - JsonPropertyInfo elementPropertyInfo = jsonPropertyInfo.CollectionElementClassInfo.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. - // Most likely this will throw JsonException unless the converter has special logic (like converter for JsonElement). - elementPropertyInfo.ReadEnumerable(JsonTokenType.Null, ref state, ref reader); - } - else - { - // Assume collection types are reference types and can have null assigned. - ApplyObjectToEnumerable(null, ref state, ref reader); - } + throw new NotImplementedException(); } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs index a83cc7beaaee..1420f2b82ee8 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs @@ -11,60 +11,12 @@ public static partial class JsonSerializer { private static void HandleStartObject(JsonSerializerOptions options, ref ReadStack state) { - Debug.Assert(!state.Current.IsProcessingDictionary && !state.Current.IsProcessingIDictionaryConstructible); - - if (state.Current.IsProcessingEnumerable || state.Current.IsProcessingICollectionConstructible) - { - // A nested object within an enumerable. - Type objType = state.Current.GetElementType(); - state.Push(); - state.Current.Initialize(objType, options); - } - else if (state.Current.JsonPropertyInfo != null) - { - // Nested object. - Type objType = state.Current.JsonPropertyInfo.RuntimePropertyType; - state.Push(); - state.Current.Initialize(objType, options); - } - - JsonClassInfo classInfo = state.Current.JsonClassInfo; - - if (state.Current.IsProcessingIDictionaryConstructible) - { - state.Current.TempDictionaryValues = (IDictionary)classInfo.CreateConcreteDictionary(); - } - else - { - state.Current.ReturnValue = classInfo.CreateObject(); - } + throw new NotImplementedException(); } private static void HandleEndObject(ref Utf8JsonReader reader, 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); - - // Check if we are trying to build the sorted cache. - if (state.Current.PropertyRefCache != null) - { - state.Current.JsonClassInfo.UpdateSortedPropertyCache(ref state.Current); - } - - object value = state.Current.ReturnValue; - - if (state.IsLastFrame) - { - state.Current.Reset(); - state.Current.ReturnValue = value; - } - else - { - state.Pop(); - ApplyObjectToEnumerable(value, ref state, ref reader); - } + throw new NotImplementedException(); } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs index ced83b472836..4b5c616d47c3 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs @@ -20,22 +20,19 @@ private static void HandlePropertyName( return; } - Debug.Assert(state.Current.ReturnValue != default || state.Current.TempDictionaryValues != default); + Debug.Assert(state.Current.ReturnValue != default || state.Current.DictionaryConverterState != default); Debug.Assert(state.Current.JsonClassInfo != default); - if ((state.Current.IsProcessingDictionary || state.Current.IsProcessingIDictionaryConstructible) && - state.Current.JsonClassInfo.DataExtensionProperty != state.Current.JsonPropertyInfo) + if (state.Current.IsProcessingDictionary && state.Current.JsonClassInfo.DataExtensionProperty != state.Current.JsonPropertyInfo) { - if (state.Current.IsDictionary || state.Current.IsIDictionaryConstructible) + if (state.Current.IsDictionary) { state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.PolicyProperty; } Debug.Assert( state.Current.IsDictionary || - (state.Current.IsDictionaryProperty && state.Current.JsonPropertyInfo != null) || - state.Current.IsIDictionaryConstructible || - (state.Current.IsIDictionaryConstructibleProperty && state.Current.JsonPropertyInfo != null)); + (state.Current.IsDictionaryProperty && state.Current.JsonPropertyInfo != null)); state.Current.KeyName = reader.GetString(); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs index c674348de9da..f145f46e63f4 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs @@ -6,11 +6,11 @@ namespace System.Text.Json { public static partial class JsonSerializer { - private static bool HandleValue(JsonTokenType tokenType, JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) + private static void HandleValue(JsonTokenType tokenType, JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) { if (state.Current.SkipProperty) { - return false; + return; } JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; @@ -23,10 +23,7 @@ private static bool HandleValue(JsonTokenType tokenType, JsonSerializerOptions o jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, typeof(object), options); } - bool lastCall = (!state.Current.IsProcessingEnumerableOrDictionary && state.Current.ReturnValue == null); - jsonPropertyInfo.Read(tokenType, ref state, ref reader); - return lastCall; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs index 60af7c75a47c..fffbfa36da51 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs @@ -68,7 +68,7 @@ private static void ReadCore( break; } } - else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingIDictionaryConstructible) + else if (readStack.Current.IsProcessingDictionary) { HandleStartDictionary(options, ref reader, ref readStack); } @@ -87,7 +87,7 @@ private static void ReadCore( // A non-dictionary property can also have EndProperty() called when completed, although it is redundant. readStack.Current.EndProperty(); } - else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingIDictionaryConstructible) + else if (readStack.Current.IsProcessingDictionary) { HandleEndDictionary(options, ref reader, ref readStack); } @@ -105,7 +105,7 @@ private static void ReadCore( } else 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)) { @@ -121,7 +121,7 @@ private static void ReadCore( } else { - HandleEndArray(options, ref reader, ref readStack); + HandleEndArray(options, ref readStack); } } else if (tokenType == JsonTokenType.Null) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs index f75673f1657e..d803f8416bf6 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs @@ -133,7 +133,7 @@ internal static void WriteDictionary( value = (TProperty)polymorphicEnumerator.Current.Value; key = polymorphicEnumerator.Current.Key; } - else if (current.IsIDictionaryConstructible || current.IsIDictionaryConstructibleProperty || current.CollectionEnumerator is IDictionaryEnumerator) + else if (current.CollectionEnumerator is IDictionaryEnumerator) { value = (TProperty)((DictionaryEntry)current.CollectionEnumerator.Current).Value; key = (string)((DictionaryEntry)current.CollectionEnumerator.Current).Key; diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs index 43d9b4763ea5..bb659bda1dd3 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs @@ -15,7 +15,7 @@ private static bool HandleEnumerable( Utf8JsonWriter writer, ref WriteStack state) { - Debug.Assert(state.Current.JsonPropertyInfo.ClassType == ClassType.Enumerable || state.Current.JsonPropertyInfo.ClassType == ClassType.ICollectionConstructible); + Debug.Assert(state.Current.JsonPropertyInfo.ClassType == ClassType.Enumerable); if (state.Current.CollectionEnumerator == null) { diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs index 86bb02ad9d25..5053133f17eb 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs @@ -122,21 +122,6 @@ private static bool HandleObject( return endOfEnumerable; } - // A property that returns a type that is deserialized by passing an - // IList to its constructor keeps the same stack frame. - if (jsonPropertyInfo.ClassType == ClassType.ICollectionConstructible) - { - state.Current.IsICollectionConstructibleProperty = true; - - bool endOfEnumerable = HandleEnumerable(jsonPropertyInfo.CollectionElementClassInfo, options, writer, ref state); - if (endOfEnumerable) - { - state.Current.MoveToNextProperty = true; - } - - return endOfEnumerable; - } - // A property that returns a dictionary keeps the same stack frame. if (jsonPropertyInfo.ClassType == ClassType.Dictionary) { @@ -149,21 +134,6 @@ private static bool HandleObject( return endOfEnumerable; } - // 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.CollectionElementClassInfo, options, writer, ref state); - if (endOfEnumerable) - { - state.Current.MoveToNextProperty = true; - } - - return endOfEnumerable; - } - // A property that returns an object. if (!obtainedValue) { diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs index c412a13bdeea..315bdf82d451 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs @@ -29,8 +29,7 @@ private static bool Write( switch (current.JsonClassInfo.ClassType) { case ClassType.Enumerable: - case ClassType.ICollectionConstructible: - finishedSerializing = HandleEnumerable(current.JsonClassInfo.ElementClassInfo, options, writer, ref state); + finishedSerializing = HandleEnumerable(current.JsonPropertyInfo.CollectionElementClassInfo, options, writer, ref state); break; case ClassType.Value: Debug.Assert(current.JsonPropertyInfo.ClassType == ClassType.Value); @@ -41,8 +40,7 @@ private static bool Write( finishedSerializing = WriteObject(options, writer, ref state); break; case ClassType.Dictionary: - case ClassType.IDictionaryConstructible: - finishedSerializing = HandleDictionary(current.JsonClassInfo.ElementClassInfo, options, writer, ref state); + finishedSerializing = HandleDictionary(current.JsonPropertyInfo.CollectionElementClassInfo, options, writer, ref state); break; default: Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Unknown); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs index 3b1b1c7b622c..47dbc1dde389 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs @@ -83,15 +83,10 @@ private void AppendStackFrame(StringBuilder sb, in ReadStackFrame frame) // For dictionaries add the key. AppendPropertyName(sb, frame.KeyName); } - else if (frame.IsProcessingEnumerable || frame.IsProcessingICollectionConstructible) + 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); - } - + IList list = frame.EnumerableConverterState.FinalList ?? frame.EnumerableConverterState.TemporaryList; if (list != null) { sb.Append(@"["); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs index 3a746ea40022..86e02652010c 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Text.Json.Serialization.Converters; namespace System.Text.Json { @@ -26,7 +27,7 @@ internal struct ReadStackFrame public JsonPropertyInfo JsonPropertyInfo; // Support System.Array and other types that don't implement IList. - public IList TempEnumerableValues; + public JsonEnumerableConverterState EnumerableConverterState; // Has an array or dictionary property been initialized. public bool CollectionPropertyInitialized; @@ -34,7 +35,7 @@ internal struct ReadStackFrame // Support IDictionary constructible types, i.e. types that we // support by passing and IDictionary to their constructors: // immutable dictionaries, Hashtable, SortedList - public IDictionary TempDictionaryValues; + public JsonDictionaryConverterState DictionaryConverterState; // For performance, we order the properties by the first deserialize and PropertyIndex helps find the right slot quicker. public int PropertyIndex; @@ -43,33 +44,25 @@ internal struct ReadStackFrame // The current JSON data for a property does not match a given POCO, so ignore the property (recursively). public bool Drain; - public bool IsCollectionForClass => IsEnumerable || IsICollectionConstructible || IsDictionary || IsIDictionaryConstructible; - public bool IsCollectionForProperty => IsEnumerableProperty || IsICollectionConstructibleProperty || IsDictionaryProperty || IsIDictionaryConstructibleProperty; + public bool IsCollectionForClass => IsEnumerable || IsDictionary; + public bool IsCollectionForProperty => IsEnumerableProperty || IsDictionaryProperty; - public bool IsIDictionaryConstructible => JsonClassInfo.ClassType == ClassType.IDictionaryConstructible; public bool IsDictionary => JsonClassInfo.ClassType == ClassType.Dictionary; public bool IsDictionaryProperty => JsonPropertyInfo != null && !JsonPropertyInfo.IsPropertyPolicy && JsonPropertyInfo.ClassType == ClassType.Dictionary; - public bool IsIDictionaryConstructibleProperty => JsonPropertyInfo != null && - !JsonPropertyInfo.IsPropertyPolicy && (JsonPropertyInfo.ClassType == ClassType.IDictionaryConstructible); - public bool IsICollectionConstructible => JsonClassInfo.ClassType == ClassType.ICollectionConstructible; public bool IsEnumerable => JsonClassInfo.ClassType == ClassType.Enumerable; public bool IsEnumerableProperty => JsonPropertyInfo != null && !JsonPropertyInfo.IsPropertyPolicy && JsonPropertyInfo.ClassType == ClassType.Enumerable; - public bool IsICollectionConstructibleProperty => JsonPropertyInfo != null && - !JsonPropertyInfo.IsPropertyPolicy && (JsonPropertyInfo.ClassType == ClassType.ICollectionConstructible); - public bool IsProcessingEnumerableOrDictionary => IsProcessingEnumerable || IsProcessingICollectionConstructible || IsProcessingDictionary || IsProcessingIDictionaryConstructible; + public bool IsProcessingEnumerableOrDictionary => IsProcessingEnumerable || IsProcessingDictionary; public bool IsProcessingDictionary => IsDictionary || IsDictionaryProperty; - public bool IsProcessingIDictionaryConstructible => IsIDictionaryConstructible || IsIDictionaryConstructibleProperty; public bool IsProcessingEnumerable => IsEnumerable || IsEnumerableProperty; - public bool IsProcessingICollectionConstructible => IsICollectionConstructible || IsICollectionConstructibleProperty; [MethodImpl(MethodImplOptions.AggressiveInlining)] // Determine whether a StartObject or StartArray token should be treated as a value. @@ -108,9 +101,7 @@ public void InitializeJsonPropertyInfo() { if (JsonClassInfo.ClassType == ClassType.Value || JsonClassInfo.ClassType == ClassType.Enumerable || - JsonClassInfo.ClassType == ClassType.ICollectionConstructible || - JsonClassInfo.ClassType == ClassType.Dictionary || - JsonClassInfo.ClassType == ClassType.IDictionaryConstructible) + JsonClassInfo.ClassType == ClassType.Dictionary) { JsonPropertyInfo = JsonClassInfo.PolicyProperty; } @@ -135,8 +126,8 @@ public void EndProperty() { CollectionPropertyInitialized = false; JsonPropertyInfo = null; - TempEnumerableValues = null; - TempDictionaryValues = null; + EnumerableConverterState = null; + DictionaryConverterState = null; JsonPropertyName = null; KeyName = null; } @@ -145,12 +136,12 @@ public Type GetElementType() { if (IsCollectionForProperty) { - return JsonPropertyInfo.CollectionElementClassInfo.Type; + return JsonPropertyInfo.CollectionElementType; } if (IsCollectionForClass) { - return JsonClassInfo.ElementClassInfo.Type; + return JsonClassInfo.PolicyProperty.CollectionElementType; } return JsonPropertyInfo.RuntimePropertyType; diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs index 48a61e22b7d2..b941e2c46266 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs @@ -14,13 +14,14 @@ internal sealed class ReflectionEmitMemberAccessor : MemberAccessor public override JsonClassInfo.ConstructorDelegate CreateConstructor(Type type) { Debug.Assert(type != null); - ConstructorInfo realMethod = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, binder: null, Type.EmptyTypes, modifiers: null); if (type.IsAbstract) { return null; } + ConstructorInfo realMethod = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, binder: null, Type.EmptyTypes, modifiers: null); + if (realMethod == null && !type.IsValueType) { return null; diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs index 15083b8cf38f..b19277fa8784 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs @@ -51,20 +51,6 @@ public void Push(JsonClassInfo nextClassInfo, object nextValue) Current.PopStackOnEndCollection = true; Current.JsonPropertyInfo = Current.JsonClassInfo.PolicyProperty; } - else if (classType == ClassType.ICollectionConstructible) - { - Current.PopStackOnEndCollection = true; - Current.JsonPropertyInfo = Current.JsonClassInfo.PolicyProperty; - - Current.IsICollectionConstructible = true; - } - 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); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs index 646cd5b42a6c..6a2503d9cca1 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs @@ -21,10 +21,6 @@ internal struct WriteStackFrame public IEnumerator CollectionEnumerator; // Note all bools are kept together for packing: public bool PopStackOnEndCollection; - public bool IsIDictionaryConstructible; - public bool IsIDictionaryConstructibleProperty; - public bool IsICollectionConstructible; - public bool IsICollectionConstructibleProperty; // The current object. public bool PopStackOnEndObject; @@ -44,16 +40,6 @@ public void Initialize(Type type, JsonSerializerOptions options) { JsonPropertyInfo = JsonClassInfo.PolicyProperty; } - else if (JsonClassInfo.ClassType == ClassType.ICollectionConstructible) - { - JsonPropertyInfo = JsonClassInfo.PolicyProperty; - IsICollectionConstructible = true; - } - else if (JsonClassInfo.ClassType == ClassType.IDictionaryConstructible) - { - JsonPropertyInfo = JsonClassInfo.PolicyProperty; - IsIDictionaryConstructible = true; - } } public void WriteObjectOrArrayStart(ClassType classType, Utf8JsonWriter writer, bool writeNull = false) @@ -72,14 +58,14 @@ public void WriteObjectOrArrayStart(ClassType classType, Utf8JsonWriter writer, 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; } else { - Debug.Assert(classType == ClassType.Enumerable || classType == ClassType.ICollectionConstructible); + Debug.Assert(classType == ClassType.Enumerable); writer.WriteStartArray(); } } @@ -92,15 +78,14 @@ private void WriteObjectOrArrayStart(ClassType classType, JsonEncodedText proper writer.WriteNull(propertyName); } else if (classType == ClassType.Object || - classType == ClassType.Dictionary || - classType == ClassType.IDictionaryConstructible) + classType == ClassType.Dictionary) { writer.WriteStartObject(propertyName); StartObjectWritten = true; } else { - Debug.Assert(classType == ClassType.Enumerable || classType == ClassType.ICollectionConstructible); + Debug.Assert(classType == ClassType.Enumerable); writer.WriteStartArray(propertyName); } } @@ -115,7 +100,6 @@ public void EndObject() { CollectionEnumerator = null; ExtensionDataStatus = ExtensionDataWriteStatus.NotStarted; - IsIDictionaryConstructible = false; JsonClassInfo = null; PropertyEnumerator = null; PropertyEnumeratorActive = false; @@ -127,7 +111,6 @@ public void EndObject() public void EndProperty() { - IsIDictionaryConstructibleProperty = false; JsonPropertyInfo = null; KeyName = null; MoveToNextProperty = false; diff --git a/src/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs index 71e588b434fb..6e83ea2a3626 100644 --- a/src/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs +++ b/src/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; -using System.Text.Json.Serialization; namespace System.Text.Json { @@ -18,20 +17,20 @@ public static void ThrowArgumentException_DeserializeWrongType(Type type, object } [MethodImpl(MethodImplOptions.NoInlining)] - public static NotSupportedException GetNotSupportedException_SerializationNotSupportedCollection(Type propertyType, Type parentType, MemberInfo memberInfo) + public static void ThrowNotSupportedException_SerializationNotSupportedCollection(Type propertyType, Type parentType, MemberInfo memberInfo) { if (parentType != null && parentType != typeof(object) && memberInfo != null) { - return new NotSupportedException(SR.Format(SR.SerializationNotSupportedCollection, propertyType, $"{parentType}.{memberInfo.Name}")); + throw new NotSupportedException(SR.Format(SR.SerializationNotSupportedCollection, propertyType, $"{parentType}.{memberInfo.Name}")); } - return new NotSupportedException(SR.Format(SR.SerializationNotSupportedCollectionType, propertyType)); + throw new NotSupportedException(SR.Format(SR.SerializationNotSupportedCollectionType, propertyType)); } [MethodImpl(MethodImplOptions.NoInlining)] - public static NotSupportedException ThrowNotSupportedException_DeserializeInstanceConstructorOfTypeNotFound(Type instanceType, Type listType) + public static void ThrowNotSupportedException_DeserializeInstanceConstructorOfTypeNotFound(Type instanceType, Type listType) { - return new NotSupportedException(SR.Format(SR.DeserializeInstanceConstructorOfTypeNotFound, instanceType, listType)); + throw new NotSupportedException(SR.Format(SR.DeserializeInstanceConstructorOfTypeNotFound, instanceType, listType)); } public static void ThrowInvalidOperationException_SerializerCycleDetected(int maxDepth) From 3d9e8bb396a30ec6be5e4d94b45bcd514be00806 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Sun, 8 Sep 2019 14:05:23 -0700 Subject: [PATCH 04/15] First sort of working version of Arrays. --- src/System.Text.Json/src/ILLinkTrim.xml | 6 +- .../DefaultDerivedEnumerableConverter.cs | 107 ++++++++-------- .../Converters/DefaultICollectionConverter.cs | 118 +++++++++++------- .../DefaultImmutableEnumerableConverter.cs | 40 +++++- .../Serialization/JsonClassInfo.Helpers.cs | 46 ++++--- .../Serialization/JsonEnumerableConverter.cs | 70 +++++++++-- .../Json/Serialization/JsonPropertyInfo.cs | 4 +- .../Text/Json/Serialization/MemberAccessor.cs | 10 ++ .../ReflectionEmitMemberAccessor.cs | 97 ++++++++++++++ .../Serialization/ReflectionMemberAccessor.cs | 56 +++++++++ 10 files changed, 422 insertions(+), 132 deletions(-) diff --git a/src/System.Text.Json/src/ILLinkTrim.xml b/src/System.Text.Json/src/ILLinkTrim.xml index 3ba61ee53be5..0fc65f6fe347 100644 --- a/src/System.Text.Json/src/ILLinkTrim.xml +++ b/src/System.Text.Json/src/ILLinkTrim.xml @@ -4,7 +4,7 @@ - + @@ -16,5 +16,9 @@ + + + + diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs index 32bc89de772f..8d33b00abea9 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs @@ -6,15 +6,14 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; -using System.Reflection; -using System.Linq; namespace System.Text.Json.Serialization.Converters { internal sealed class DefaultDerivedEnumerableConverter : JsonEnumerableConverter { - // Cache concrete list constructors for performance. + // Cache constructors for performance. private static readonly Dictionary s_ctors = new Dictionary(); + private static readonly Dictionary s_collectonBuilderCtors = new Dictionary(); public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) { @@ -24,14 +23,19 @@ public override bool OwnsImplementedCollectionType(Type implementedCollectionTyp public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) { - if (jsonPropertyInfo.DeclaredPropertyType.IsInterface) + Type implementedCollectionPropertyType = jsonPropertyInfo.ImplementedCollectionPropertyType; + + if (implementedCollectionPropertyType.IsInterface) { - if (jsonPropertyInfo.DeclaredPropertyType.IsGenericType) + if (implementedCollectionPropertyType.IsGenericType) { - if (typeof(ISet<>).MakeGenericType(jsonPropertyInfo.CollectionElementType).IsAssignableFrom(jsonPropertyInfo.DeclaredPropertyType)) - return typeof(HashSet<>).MakeGenericType(jsonPropertyInfo.CollectionElementType); - if (typeof(ICollection<>).MakeGenericType(jsonPropertyInfo.CollectionElementType).IsAssignableFrom(jsonPropertyInfo.DeclaredPropertyType)) - return typeof(Collection<>).MakeGenericType(jsonPropertyInfo.CollectionElementType); + switch (implementedCollectionPropertyType.GetGenericTypeDefinition().FullName) + { + case JsonClassInfo.SetGenericInterfaceTypeName: + return typeof(HashSet<>).MakeGenericType(jsonPropertyInfo.CollectionElementType); + case JsonClassInfo.CollectionGenericInterfaceTypeName: + return typeof(Collection<>).MakeGenericType(jsonPropertyInfo.CollectionElementType); + } } return typeof(List<>).MakeGenericType(jsonPropertyInfo.CollectionElementType); } @@ -43,11 +47,7 @@ public override void BeginEnumerable(ref ReadStack state, JsonSerializerOptions { Debug.Assert(state.Current.EnumerableConverterState == null); - JsonClassInfo.ConstructorDelegate ctor = state.Current.JsonPropertyInfo.DeclaredPropertyType.IsInterface - ? FindCachedCtor(state.Current.JsonPropertyInfo.RuntimePropertyType, options) - : state.Current.JsonPropertyInfo.DeclaredClassInfo.CreateObject; - - object instance = ctor(); + object instance = CreateConcreteInstance(ref state, options); if (instance is IList list) { @@ -58,39 +58,11 @@ public override void BeginEnumerable(ref ReadStack state, JsonSerializerOptions } else { - //var c = typeof(ImmutableEnumerableCreator<,>).MakeGenericType(typeof(int), typeof(List<>).MakeGenericType(typeof(int))).GetConstructors(); - - //var Test = typeof(JsonEnumerableConverterStateCollection).GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, binder: null, Type.EmptyTypes, modifiers: null); - - //var c = new JsonEnumerableConverterStateCollection(); - - Type collectionType = typeof(JsonEnumerableConverterState.Collection<>).MakeGenericType(state.Current.JsonPropertyInfo.CollectionElementType); - - //var p = Activator.CreateInstance(collectionType); - - JsonEnumerableConverterState.Collection CollectionInstance = (JsonEnumerableConverterState.Collection)FindCachedCtor( - collectionType, - options)(); - CollectionInstance.Instance = instance; - - /*Type CollectionElementType = state.Current.JsonPropertyInfo.CollectionElementType; - - MethodInfo AddMethod = instance - .GetType() - .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .FirstOrDefault(m => - { - ParameterInfo[] Parameters = m.GetParameters(); - return (m.Name == "Add" || m.Name == "System.Collections.Generic.ICollection.Add") && - m.ReturnType == typeof(void) && - Parameters.Length == 1 && - Parameters[0].ParameterType == CollectionElementType; - });*/ + Type collectionType = typeof(JsonEnumerableConverterState.CollectionBuilder<>).MakeGenericType(state.Current.JsonPropertyInfo.CollectionElementType); state.Current.EnumerableConverterState = new JsonEnumerableConverterState { - FinalCollection = instance, - //CollectionAddAction = (Action)AddMethod.CreateDelegate(typeof(Action)) + FinalCollection = CreateCollectionBuilderInstance(collectionType, instance, options) }; } } @@ -106,7 +78,7 @@ public override void AddItemToEnumerable(ref ReadStack state, JsonSerializerOpti } else { - //state.Current.EnumerableConverterState.CollectionAddAction(value); + state.Current.EnumerableConverterState.FinalCollection.Add(value); } } @@ -115,20 +87,57 @@ public override object EndEnumerable(ref ReadStack state, JsonSerializerOptions Debug.Assert(state.Current.EnumerableConverterState?.FinalList != null || state.Current.EnumerableConverterState.FinalCollection != null); - return state.Current.EnumerableConverterState.FinalList ?? state.Current.EnumerableConverterState.FinalCollection; + return state.Current.EnumerableConverterState.FinalList ?? state.Current.EnumerableConverterState.FinalCollection.Instance; + } + + private object CreateConcreteInstance(ref ReadStack state, JsonSerializerOptions options) + { + if (state.Current.JsonPropertyInfo.DeclaredPropertyType.IsInterface) + { + JsonClassInfo.ConstructorDelegate ctor = FindCachedCtor(state.Current.JsonPropertyInfo.RuntimePropertyType, options); + if (ctor == null) + { + ThrowHelper.ThrowInvalidOperationException_DeserializePolymorphicInterface(state.Current.JsonPropertyInfo.RuntimePropertyType); + } + return ctor(); + } + else + { + return state.Current.JsonPropertyInfo.DeclaredClassInfo.CreateObject(); + } + } + + private JsonEnumerableConverterState.CollectionBuilder CreateCollectionBuilderInstance(Type collectionType, object instance, JsonSerializerOptions options) + { + JsonEnumerableConverterState.CollectionBuilderConstructorDelegate ctor = FindCachedCollectionBuilderCtor(collectionType, options); + Debug.Assert(ctor != null); + return ctor(instance); } - private JsonClassInfo.ConstructorDelegate FindCachedCtor(Type runtimePropertyType, JsonSerializerOptions options) + private JsonClassInfo.ConstructorDelegate FindCachedCtor(Type type, JsonSerializerOptions options) { - string key = runtimePropertyType.FullName; + string key = type.FullName; if (!s_ctors.TryGetValue(key, out JsonClassInfo.ConstructorDelegate ctor)) { - ctor = options.MemberAccessorStrategy.CreateConstructor(runtimePropertyType); + ctor = options.MemberAccessorStrategy.CreateConstructor(type); s_ctors[key] = ctor; } return ctor; } + + private JsonEnumerableConverterState.CollectionBuilderConstructorDelegate FindCachedCollectionBuilderCtor(Type collectionType, JsonSerializerOptions options) + { + string key = collectionType.FullName; + + if (!s_collectonBuilderCtors.TryGetValue(key, out JsonEnumerableConverterState.CollectionBuilderConstructorDelegate ctor)) + { + ctor = options.MemberAccessorStrategy.CreateCollectionBuilderConstructor(collectionType); + s_collectonBuilderCtors[key] = ctor; + } + + return ctor; + } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs index d72547ed256c..0ed3f45cc60a 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs @@ -4,84 +4,99 @@ using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; namespace System.Text.Json.Serialization.Converters { internal sealed class DefaultICollectionConverter : JsonTemporaryListConverter { + // Cache factories for performance. + private static readonly Dictionary s_factories = new Dictionary(); + public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) { - //Queues, Stacks, SortedSets, readonly collections - return false; + return JsonClassInfo.IsDeserializedByConstructingWithIList(implementedCollectionType); } public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) { - // Only things you can't spin on should go here. + Type implementedCollectionPropertyType = jsonPropertyInfo.ImplementedCollectionPropertyType; + + if (implementedCollectionPropertyType.IsInterface) + { + if (implementedCollectionPropertyType.IsGenericType) + { + switch (implementedCollectionPropertyType.GetGenericTypeDefinition().FullName) + { + case JsonClassInfo.ReadOnlyCollectionGenericInterfaceTypeName: + case JsonClassInfo.ReadOnlyListGenericInterfaceTypeName: + return typeof(ReadOnlyCollection<>).MakeGenericType(jsonPropertyInfo.CollectionElementType); + } + } + + ThrowHelper.ThrowInvalidOperationException_DeserializePolymorphicInterface(implementedCollectionPropertyType); + } + + return jsonPropertyInfo.DeclaredPropertyType; + } + + protected override Type ResolveTemporaryListType(JsonPropertyInfo jsonPropertyInfo) + { + Type implementedCollectionPropertyType = jsonPropertyInfo.ImplementedCollectionPropertyType; - // todo: Figure out what the runtime type was before for these collections. + if (implementedCollectionPropertyType.IsGenericType && + implementedCollectionPropertyType.GetGenericTypeDefinition().FullName == JsonClassInfo.ReadOnlyObservableCollectionGenericTypeName) + { + return implementedCollectionPropertyType.Assembly.GetType(JsonClassInfo.ObservableCollectionGenericTypeName); + } - return typeof(List<>).MakeGenericType(jsonPropertyInfo.CollectionElementType); + return base.ResolveTemporaryListType(jsonPropertyInfo); } public override object EndEnumerable(ref ReadStack state, JsonSerializerOptions options) { Debug.Assert(state.Current.EnumerableConverterState?.TemporaryList != null); + IList sourceList = state.Current.EnumerableConverterState.TemporaryList; + Type collectionType = state.Current.JsonPropertyInfo.RuntimePropertyType; + try { - /* - // Note: Types are defined explicityly here for performance. - if (parentType.IsGenericType) + if (collectionType.IsGenericType) { - Type genericTypeDefinition = parentType.GetGenericTypeDefinition(); - - IList typedList = (IList)sourceList; - - if (genericTypeDefinition == typeof(Stack<>)) - { - return new Stack(typedList); - } - else if (genericTypeDefinition == typeof(Queue<>)) + switch (collectionType.GetGenericTypeDefinition().FullName) { - return new Queue(typedList); - } - else if (genericTypeDefinition == typeof(HashSet<>)) - { - return new HashSet(typedList); - } - else if (genericTypeDefinition == typeof(LinkedList<>)) - { - return new LinkedList(typedList); - } - else if (genericTypeDefinition == typeof(ReadOnlyCollection<>)) - { - return new ReadOnlyCollection(typedList); - } - else if (genericTypeDefinition.FullName == JsonClassInfo.ReadOnlyObservableCollectionGenericTypeName) - { - // new ObservableCollection(typedList) - object ObservableCollection = Activator.CreateInstance( - parentType.Assembly.GetType(JsonClassInfo.ObservableCollectionGenericTypeName).MakeGenericType(typeof(TDeclaredProperty)), - typedList); - - // new ReadOnlyObservableCollection(ObservableCollection); - return (IEnumerable)Activator.CreateInstance(parentType, ObservableCollection); + case JsonClassInfo.ReadOnlyCollectionGenericTypeName: + case JsonClassInfo.ReadOnlyObservableCollectionGenericTypeName: + case JsonClassInfo.StackGenericTypeName: + case JsonClassInfo.QueueGenericTypeName: + case JsonClassInfo.SortedSetGenericTypeName: + return CreateEnumerableInstance( + collectionType, + sourceList, + options); } } else { - if (parentType == typeof(ArrayList)) + if (collectionType == typeof(ArrayList)) { return new ArrayList(sourceList); } - //Stack & Queue would require a reference to System.Collections.NonGeneric + switch (collectionType.FullName) + { + case JsonClassInfo.StackTypeName: + case JsonClassInfo.QueueTypeName: + return CreateEnumerableInstance( + collectionType, + sourceList, + options); + } } - */ - return (IEnumerable)Activator.CreateInstance(state.Current.JsonPropertyInfo.DeclaredPropertyType, state.Current.EnumerableConverterState.TemporaryList); + return Activator.CreateInstance(collectionType, state.Current.EnumerableConverterState.TemporaryList); } catch (MissingMethodException) { @@ -89,5 +104,20 @@ public override object EndEnumerable(ref ReadStack state, JsonSerializerOptions return null; } } + + private object CreateEnumerableInstance(Type collectionType, IList temporaryList, JsonSerializerOptions options) + { + Debug.Assert(collectionType != null); + + string key = collectionType.FullName; + + if (!s_factories.TryGetValue(key, out JsonEnumerableConverterState.WrappedEnumerableFactory factory)) + { + factory = options.MemberAccessorStrategy.CreateWrappedEnumerableFactoryConstructor(collectionType, temporaryList.GetType())(options); + s_factories[key] = factory; + } + + return factory.CreateFromList(temporaryList); + } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs index 3cebb08eb9fb..288797b7637e 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs @@ -112,21 +112,51 @@ public override bool OwnsImplementedCollectionType(Type implementedCollectionTyp public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) { - // todo: Figure out what the runtime type was before for these collections. + Type implementedCollectionPropertyType = jsonPropertyInfo.ImplementedCollectionPropertyType; + Type collectionElementType = jsonPropertyInfo.CollectionElementType; + if (implementedCollectionPropertyType.IsInterface) + { + Type runtimeType = null; + switch (implementedCollectionPropertyType.GetGenericTypeDefinition().FullName) + { + case ImmutableListGenericInterfaceTypeName: + runtimeType = ResolveConcreteImmutableType(implementedCollectionPropertyType, collectionElementType, ImmutableListGenericTypeName); + break; + case ImmutableQueueGenericInterfaceTypeName: + runtimeType = ResolveConcreteImmutableType(implementedCollectionPropertyType, collectionElementType, ImmutableQueueGenericTypeName); + break; + case ImmutableSetGenericInterfaceTypeName: + runtimeType = ResolveConcreteImmutableType(implementedCollectionPropertyType, collectionElementType, ImmutableHashSetGenericTypeName); + break; + case ImmutableStackGenericInterfaceTypeName: + runtimeType = ResolveConcreteImmutableType(implementedCollectionPropertyType, collectionElementType, ImmutableStackGenericInterfaceTypeName); + break; + } + if (runtimeType == null) + { + ThrowHelper.ThrowInvalidOperationException_DeserializePolymorphicInterface(implementedCollectionPropertyType); + } + return runtimeType; + } - return typeof(List<>).MakeGenericType(jsonPropertyInfo.CollectionElementType); + return jsonPropertyInfo.DeclaredPropertyType; + } + + private static Type ResolveConcreteImmutableType(Type implementedCollectionPropertyType, Type collectionElementType, string typeName) + { + return implementedCollectionPropertyType.Assembly.GetType(typeName)?.MakeGenericType(collectionElementType); } public override object EndEnumerable(ref ReadStack state, JsonSerializerOptions options) { Debug.Assert(state.Current.EnumerableConverterState?.TemporaryList != null); - Type immutableCollectionType = state.Current.JsonPropertyInfo.RuntimePropertyType; + Type collectionType = state.Current.JsonPropertyInfo.RuntimePropertyType; Type elementType = state.Current.JsonPropertyInfo.CollectionElementType; - string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out _, out _); + string delegateKey = GetDelegateKey(collectionType, elementType, out _, out _); - return CreateImmutableCollectionInstance(ref state, immutableCollectionType, delegateKey, state.Current.EnumerableConverterState.TemporaryList, options); + return CreateImmutableCollectionInstance(ref state, collectionType, delegateKey, state.Current.EnumerableConverterState.TemporaryList, options); } // Creates an IEnumerable and populates it with the items in the diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs index 5fd9fbfff2ba..ca8e36171000 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs @@ -22,16 +22,16 @@ internal partial class JsonClassInfo 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"; + public const string CollectionGenericInterfaceTypeName = "System.Collections.Generic.ICollection`1"; private const string CollectionGenericTypeName = "System.Collections.ObjectModel.Collection`1"; - internal const string ObservableCollectionGenericTypeName = "System.Collections.ObjectModel.ObservableCollection`1"; + public const string ObservableCollectionGenericTypeName = "System.Collections.ObjectModel.ObservableCollection`1"; private const string CollectionInterfaceTypeName = "System.Collections.ICollection"; - private const string ReadOnlyListGenericInterfaceTypeName = "System.Collections.Generic.IReadOnlyList`1"; + public const string ReadOnlyListGenericInterfaceTypeName = "System.Collections.Generic.IReadOnlyList`1"; - private const string ReadOnlyCollectionGenericInterfaceTypeName = "System.Collections.Generic.IReadOnlyCollection`1"; - private const string ReadOnlyCollectionGenericTypeName = "System.Collections.ObjectModel.ReadOnlyCollection`1"; - internal const string ReadOnlyObservableCollectionGenericTypeName = "System.Collections.ObjectModel.ReadOnlyObservableCollection`1"; + public const string ReadOnlyCollectionGenericInterfaceTypeName = "System.Collections.Generic.IReadOnlyCollection`1"; + public const string ReadOnlyCollectionGenericTypeName = "System.Collections.ObjectModel.ReadOnlyCollection`1"; + public const string ReadOnlyObservableCollectionGenericTypeName = "System.Collections.ObjectModel.ReadOnlyObservableCollection`1"; public const string HashtableTypeName = "System.Collections.Hashtable"; public const string SortedListTypeName = "System.Collections.SortedList"; @@ -206,22 +206,21 @@ public static Type GetImplementedCollectionType( return typeof(IEnumerable); } - public static bool IsSetInterface(Type type) - { - return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ISet<>); - } - public static bool IsDeserializedByConstructingWithIList(Type type) { if (type.IsGenericType) { switch (type.GetGenericTypeDefinition().FullName) { + // interfaces + case ReadOnlyCollectionGenericInterfaceTypeName: + case ReadOnlyListGenericInterfaceTypeName: + // types case ReadOnlyCollectionGenericTypeName: case ReadOnlyObservableCollectionGenericTypeName: case StackGenericTypeName: case QueueGenericTypeName: - case LinkedListGenericTypeName: + case SortedSetGenericTypeName: return true; default: return false; @@ -243,10 +242,23 @@ public static bool IsDeserializedByConstructingWithIDictionary(Type type) { if (type.IsGenericType) { - return type.GetGenericTypeDefinition().FullName == ReadOnlyDictionaryGenericTypeName; + switch (type.GetGenericTypeDefinition().FullName) + { + case ReadOnlyDictionaryGenericTypeName: + case SortedDictionaryGenericTypeName: + return true; + default: + return false; + } } - return false; + switch (type.FullName) + { + case SortedListTypeName: + return true; + default: + return false; + } } public static bool IsNativelySupportedCollection(Type queryType) @@ -261,12 +273,6 @@ public static bool IsNativelySupportedCollection(Type queryType) return s_nativelySupportedNonGenericCollections.Contains(queryType.FullName); } - public static bool IsGenericDictionary(Type type) - { - return type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IDictionary<,>) || - type.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)); - } - // The following methods were copied verbatim from AspNetCore: // https://github.com/aspnet/AspNetCore/blob/13ae0057fbb11fd84fcee8fca46ebc1b2d7c1e6a/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs. diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs index 9a05156533ea..b2ef09f0726a 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs @@ -10,27 +10,70 @@ namespace System.Text.Json.Serialization.Converters { internal class JsonEnumerableConverterState { - public abstract class Collection + public delegate CollectionBuilder CollectionBuilderConstructorDelegate(object instance); + public delegate WrappedEnumerableFactory WrappedEnumerableFactoryConstructorDelegate(JsonSerializerOptions options); + public delegate object EnumerableConstructorDelegate(TSourceList sourceList) where TSourceList : IEnumerable; + + public abstract class CollectionBuilder { - public object Instance; + public abstract object Instance { get; } + public abstract void Add(object item); } - public sealed class Collection : Collection + public sealed class CollectionBuilder : CollectionBuilder { + private readonly ICollection _instance; + + public override object Instance => _instance; + + public CollectionBuilder(object instance) + { + Debug.Assert(instance != null && instance is ICollection); + _instance = (ICollection)instance; + } + public override void Add(object item) { - Debug.Assert(Instance != null && - typeof(ICollection).IsAssignableFrom(Instance.GetType()) && - (item == null || item.GetType() == typeof(T))); - ((ICollection)Instance).Add((T)item); + Debug.Assert(item == null || item.GetType() == typeof(T)); + _instance.Add((T)item); + } + } + + public abstract class WrappedEnumerableFactory + { + public abstract object CreateFromList(IEnumerable sourceList); + } + + public sealed class WrappedEnumerableFactory : WrappedEnumerableFactory + where TCollection : IEnumerable + where TSourceList : IEnumerable + { + private readonly EnumerableConstructorDelegate _ctor; + + public WrappedEnumerableFactory(JsonSerializerOptions options) + { + Debug.Assert(options != null); + + _ctor = options.MemberAccessorStrategy.CreateEnumerableConstructor(); + } + + public override object CreateFromList(IEnumerable sourceList) + { + Debug.Assert(sourceList != null && sourceList is TSourceList); + + if (_ctor == null) + { + ThrowHelper.ThrowNotSupportedException_DeserializeInstanceConstructorOfTypeNotFound(typeof(TCollection), sourceList.GetType()); + } + + return _ctor((TSourceList)sourceList); } } public IList TemporaryList; public IList FinalList; - public object FinalCollection; - //public Action CollectionAddAction; + public CollectionBuilder FinalCollection; } internal abstract class JsonTemporaryListConverter : JsonEnumerableConverter @@ -55,15 +98,20 @@ public override void AddItemToEnumerable(ref ReadStack state, JsonSerializerOpti state.Current.EnumerableConverterState.TemporaryList.Add(value); } + protected virtual Type ResolveTemporaryListType(JsonPropertyInfo jsonPropertyInfo) + => typeof(List<>); + private IList CreateConcreteList(JsonPropertyInfo jsonPropertyInfo, JsonSerializerOptions options) { Debug.Assert(jsonPropertyInfo?.CollectionElementType != null); - string key = jsonPropertyInfo.CollectionElementType.FullName; + Type TemporaryListType = ResolveTemporaryListType(jsonPropertyInfo).MakeGenericType(jsonPropertyInfo.CollectionElementType); + + string key = TemporaryListType.FullName; if (!s_ctors.TryGetValue(key, out JsonClassInfo.ConstructorDelegate ctor)) { - ctor = options.MemberAccessorStrategy.CreateConstructor(typeof(List<>).MakeGenericType(jsonPropertyInfo.CollectionElementType)); + ctor = options.MemberAccessorStrategy.CreateConstructor(TemporaryListType); s_ctors[key] = ctor; } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs index f92578f77efe..76e1890e3bdc 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs @@ -461,9 +461,9 @@ private void DetermineEnumerableOrDictionaryConverter() { EnumerableConverter = s_jsonImmutableEnumerableConverter; - DefaultImmutableEnumerableConverter.RegisterImmutableCollection(RuntimePropertyType, CollectionElementType, Options); - RuntimePropertyType = EnumerableConverter.ResolveRunTimeType(this); + + DefaultImmutableEnumerableConverter.RegisterImmutableCollection(RuntimePropertyType, CollectionElementType, Options); } else if (s_jsonICollectionConverter.OwnsImplementedCollectionType(ImplementedCollectionPropertyType, CollectionElementType)) { diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs index bb513ac62486..234d2a08f20f 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs @@ -2,8 +2,10 @@ // 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.Diagnostics; using System.Reflection; +using System.Text.Json.Serialization.Converters; namespace System.Text.Json { @@ -11,6 +13,14 @@ internal abstract class MemberAccessor { public abstract JsonClassInfo.ConstructorDelegate CreateConstructor(Type classType); + public abstract JsonEnumerableConverterState.CollectionBuilderConstructorDelegate CreateCollectionBuilderConstructor(Type collectionType); + + public abstract JsonEnumerableConverterState.WrappedEnumerableFactoryConstructorDelegate CreateWrappedEnumerableFactoryConstructor(Type collectionType, Type sourceListType); + + public abstract JsonEnumerableConverterState.EnumerableConstructorDelegate CreateEnumerableConstructor() + where TCollection : IEnumerable + where TSourceList : IEnumerable; + public abstract ImmutableCollectionCreator ImmutableCollectionCreateRange(Type constructingType, Type collectionType, Type elementType); public abstract ImmutableCollectionCreator ImmutableDictionaryCreateRange(Type constructingType, Type collectionType, Type elementType); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs index b941e2c46266..e501ac8c33fa 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; +using System.Text.Json.Serialization.Converters; namespace System.Text.Json { @@ -55,6 +56,102 @@ public override JsonClassInfo.ConstructorDelegate CreateConstructor(Type type) return (JsonClassInfo.ConstructorDelegate)dynamicMethod.CreateDelegate(typeof(JsonClassInfo.ConstructorDelegate)); } + public override JsonEnumerableConverterState.CollectionBuilderConstructorDelegate CreateCollectionBuilderConstructor(Type collectionType) + { + Debug.Assert(collectionType != null); + + ConstructorInfo realMethod = collectionType.GetConstructor( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + binder: null, + new Type[] { typeof(object) }, + modifiers: null); + + if (realMethod == null) + { + return null; + } + + var dynamicMethod = new DynamicMethod( + ConstructorInfo.ConstructorName, + typeof(JsonEnumerableConverterState.CollectionBuilder), + new Type[] { typeof(object) }, + typeof(ReflectionEmitMemberAccessor).Module, + skipVisibility: true); + + ILGenerator generator = dynamicMethod.GetILGenerator(); + + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Newobj, realMethod); + generator.Emit(OpCodes.Ret); + + return (JsonEnumerableConverterState.CollectionBuilderConstructorDelegate)dynamicMethod.CreateDelegate( + typeof(JsonEnumerableConverterState.CollectionBuilderConstructorDelegate)); + } + + public override JsonEnumerableConverterState.WrappedEnumerableFactoryConstructorDelegate CreateWrappedEnumerableFactoryConstructor(Type collectionType, Type sourceListType) + { + Debug.Assert(collectionType != null && sourceListType != null); + + Type factoryType = typeof(JsonEnumerableConverterState.WrappedEnumerableFactory<,>).MakeGenericType(collectionType, sourceListType); + + ConstructorInfo realMethod = factoryType.GetConstructor( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + binder: null, + new Type[] { typeof(JsonSerializerOptions) }, + modifiers: null); + + if (realMethod == null) + { + return null; + } + + var dynamicMethod = new DynamicMethod( + ConstructorInfo.ConstructorName, + typeof(JsonEnumerableConverterState.WrappedEnumerableFactory), + new Type[] { typeof(JsonSerializerOptions) }, + typeof(ReflectionEmitMemberAccessor).Module, + skipVisibility: true); + + ILGenerator generator = dynamicMethod.GetILGenerator(); + + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Newobj, realMethod); + generator.Emit(OpCodes.Ret); + + return (JsonEnumerableConverterState.WrappedEnumerableFactoryConstructorDelegate)dynamicMethod.CreateDelegate( + typeof(JsonEnumerableConverterState.WrappedEnumerableFactoryConstructorDelegate)); + } + + public override JsonEnumerableConverterState.EnumerableConstructorDelegate CreateEnumerableConstructor() + { + ConstructorInfo realMethod = typeof(TCollection).GetConstructor( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + binder: null, + new Type[] { typeof(TSourceList) }, + modifiers: null); + + if (realMethod == null) + { + return null; + } + + var dynamicMethod = new DynamicMethod( + ConstructorInfo.ConstructorName, + typeof(object), + new Type[] { typeof(TSourceList) }, + typeof(ReflectionEmitMemberAccessor).Module, + skipVisibility: true); + + ILGenerator generator = dynamicMethod.GetILGenerator(); + + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Newobj, realMethod); + generator.Emit(OpCodes.Ret); + + return (JsonEnumerableConverterState.EnumerableConstructorDelegate)dynamicMethod.CreateDelegate( + typeof(JsonEnumerableConverterState.EnumerableConstructorDelegate)); + } + public override ImmutableCollectionCreator ImmutableCollectionCreateRange(Type constructingType, Type collectionType, Type elementType) { MethodInfo createRange = ImmutableCollectionCreateRangeMethod(constructingType, elementType); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs index 2214e79cdf36..a43ed181f24e 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs @@ -2,9 +2,11 @@ // 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.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; +using System.Text.Json.Serialization.Converters; namespace System.Text.Json { @@ -42,6 +44,60 @@ public override JsonClassInfo.ConstructorDelegate CreateConstructor(Type type) return () => Activator.CreateInstance(type); } + public override JsonEnumerableConverterState.CollectionBuilderConstructorDelegate CreateCollectionBuilderConstructor(Type collectionType) + { + Debug.Assert(collectionType != null); + + ConstructorInfo realMethod = collectionType.GetConstructor( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + binder: null, + new Type[] { typeof(object) }, + modifiers: null); + + if (realMethod == null) + { + return null; + } + + return (object instance) => (JsonEnumerableConverterState.CollectionBuilder)Activator.CreateInstance(collectionType, instance); + } + + public override JsonEnumerableConverterState.WrappedEnumerableFactoryConstructorDelegate CreateWrappedEnumerableFactoryConstructor(Type collectionType, Type sourceListType) + { + Debug.Assert(collectionType != null && sourceListType != null); + + Type factoryType = typeof(JsonEnumerableConverterState.WrappedEnumerableFactory<,>).MakeGenericType(collectionType, sourceListType); + + ConstructorInfo realMethod = factoryType.GetConstructor( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + binder: null, + new Type[] { typeof(JsonSerializerOptions) }, + modifiers: null); + + if (realMethod == null) + { + return null; + } + + return (JsonSerializerOptions options) => (JsonEnumerableConverterState.WrappedEnumerableFactory)Activator.CreateInstance(factoryType, options); + } + + public override JsonEnumerableConverterState.EnumerableConstructorDelegate CreateEnumerableConstructor() + { + ConstructorInfo realMethod = typeof(TCollection).GetConstructor( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + binder: null, + new Type[] { typeof(TSourceList) }, + modifiers: null); + + if (realMethod == null) + { + return null; + } + + return (TSourceList sourceList) => (JsonEnumerableConverterState.WrappedEnumerableFactory)Activator.CreateInstance(typeof(TCollection), sourceList); + } + public override ImmutableCollectionCreator ImmutableCollectionCreateRange(Type constructingType, Type collectionType, Type elementType) { MethodInfo createRange = ImmutableCollectionCreateRangeMethod(constructingType, elementType); From 9ab86b2cbd419072ba99ab8dd68955c15f6c731e Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Mon, 9 Sep 2019 14:24:41 -0700 Subject: [PATCH 05/15] Added back in dictionary support. --- src/System.Text.Json/src/ILLinkTrim.xml | 6 +- .../DefaultDerivedDictionaryConverter.cs | 109 +++++++++-------- .../DefaultDerivedEnumerableConverter.cs | 50 ++++---- .../Converters/DefaultICollectionConverter.cs | 61 ++-------- .../Converters/DefaultIDictionaryConverter.cs | 59 +++++++--- .../DefaultImmutableDictionaryConverter.cs | 24 ++-- .../DefaultImmutableEnumerableConverter.cs | 1 - .../Text/Json/Serialization/JsonClassInfo.cs | 9 +- .../Serialization/JsonDictionaryConverter.cs | 110 ++++++++++++++++-- .../Serialization/JsonEnumerableConverter.cs | 37 +++--- .../JsonSerializer.Read.HandleArray.cs | 15 +-- .../JsonSerializer.Read.HandleDictionary.cs | 54 ++++++++- .../JsonSerializer.Read.HandleNull.cs | 85 +++++++++++++- .../JsonSerializer.Read.HandleObject.cs | 47 +++++++- .../Json/Serialization/JsonSerializer.Read.cs | 8 +- .../Text/Json/Serialization/MemberAccessor.cs | 8 ++ .../Text/Json/Serialization/ReadStack.cs | 7 +- .../Text/Json/Serialization/ReadStackFrame.cs | 16 --- .../ReflectionEmitMemberAccessor.cs | 96 +++++++++++++++ .../Serialization/ReflectionMemberAccessor.cs | 54 +++++++++ 20 files changed, 636 insertions(+), 220 deletions(-) diff --git a/src/System.Text.Json/src/ILLinkTrim.xml b/src/System.Text.Json/src/ILLinkTrim.xml index 0fc65f6fe347..8a4bbb90f729 100644 --- a/src/System.Text.Json/src/ILLinkTrim.xml +++ b/src/System.Text.Json/src/ILLinkTrim.xml @@ -16,7 +16,11 @@ - + + + + + diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs index 47e582b6cc30..60f11fa43570 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; @@ -10,9 +11,14 @@ namespace System.Text.Json.Serialization.Converters { internal sealed class DefaultDerivedDictionaryConverter : JsonDictionaryConverter { + // Cache constructors for performance. + private static readonly ConcurrentDictionary s_ctors = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary s_dictionaryBuilderCtors = new ConcurrentDictionary(); + public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) { - throw new NotImplementedException(); + return typeof(IDictionary).IsAssignableFrom(implementedCollectionType) || + (implementedCollectionType.IsGenericType && typeof(IDictionary<,>).MakeGenericType(typeof(string), collectionElementType).IsAssignableFrom(implementedCollectionType)); } public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) @@ -27,81 +33,92 @@ public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) public override void BeginDictionary(ref ReadStack state, JsonSerializerOptions options) { - Debug.Assert(state.Current.EnumerableConverterState == null); + Debug.Assert(state.Current.DictionaryConverterState == null); - if (state.Current.JsonPropertyInfo.DeclaredPropertyType.IsInterface) - { - state.Current.DictionaryConverterState = new JsonDictionaryConverterState - { - FinalInstance = (IDictionary)state.Current.JsonPropertyInfo.RuntimeClassInfo.CreateObject() - }; - } - else if (state.Current.JsonPropertyInfo.DeclaredPropertyType == state.Current.JsonPropertyInfo.RuntimePropertyType) + object instance = CreateConcreteInstance(ref state, options); + + if (instance is IDictionary dictionary) { state.Current.DictionaryConverterState = new JsonDictionaryConverterState { - FinalInstance = (IDictionary)state.Current.JsonPropertyInfo.DeclaredClassInfo.CreateObject() + FinalDictionary = dictionary }; } else { + Type dictionaryType = typeof(JsonDictionaryConverterState.DictionaryBuilder<>).MakeGenericType(state.Current.JsonPropertyInfo.CollectionElementType); + state.Current.DictionaryConverterState = new JsonDictionaryConverterState { - TemporaryDictionary = (IDictionary)state.Current.JsonPropertyInfo.RuntimeClassInfo.CreateObject() + Builder = CreateDictionaryBuilderInstance(dictionaryType, instance, options) }; } } - public override void AddItemToDictionary(ref ReadStack state, JsonSerializerOptions options, string key, object value) + public override void AddItemToDictionary(ref ReadStack state, JsonSerializerOptions options, string key, ref T value) { - Debug.Assert(state.Current.DictionaryConverterState == null); + Debug.Assert(state.Current.DictionaryConverterState?.FinalDictionary != null || + state.Current.DictionaryConverterState.Builder != null); - JsonDictionaryConverterState convertState = state.Current.DictionaryConverterState; + IDictionary finalDictionary = state.Current.DictionaryConverterState.FinalDictionary; - (convertState.FinalInstance ?? convertState.TemporaryDictionary).Add(key, value); + if (finalDictionary != null) + { + if (finalDictionary is IDictionary typedDictionary) + { + typedDictionary.Add(key, value); + } + else + { + finalDictionary.Add(key, value); + } + } + else + { + state.Current.DictionaryConverterState.Builder.Add(key, ref value); + } } public override object EndDictionary(ref ReadStack state, JsonSerializerOptions options) { - Debug.Assert(state.Current.DictionaryConverterState != null); - - JsonDictionaryConverterState convertState = state.Current.DictionaryConverterState; + Debug.Assert(state.Current.DictionaryConverterState?.FinalDictionary != null || + state.Current.DictionaryConverterState.Builder != null); - if (convertState.FinalInstance != null) - return convertState.FinalInstance; - - object instance = state.Current.JsonPropertyInfo.DeclaredClassInfo.CreateObject(); + return state.Current.DictionaryConverterState.FinalDictionary ?? state.Current.DictionaryConverterState.Builder.Instance; + } - if (instance is IDictionary instanceOfIDictionary) + private object CreateConcreteInstance(ref ReadStack state, JsonSerializerOptions options) + { + if (state.Current.JsonPropertyInfo.DeclaredPropertyType.IsInterface) { - if (!instanceOfIDictionary.IsReadOnly) + JsonClassInfo.ConstructorDelegate ctor = FindCachedCtor(state.Current.JsonPropertyInfo.RuntimePropertyType, options); + if (ctor == null) { - foreach (DictionaryEntry entry in convertState.TemporaryDictionary) - { - instanceOfIDictionary.Add((string)entry.Key, entry.Value); - } - return instanceOfIDictionary; + ThrowHelper.ThrowInvalidOperationException_DeserializePolymorphicInterface(state.Current.JsonPropertyInfo.RuntimePropertyType); } + return ctor(); } - /* - else if (instance is IDictionary instanceOfGenericIDictionary) + else { - if (!instanceOfGenericIDictionary.IsReadOnly) - { - foreach (DictionaryEntry entry in sourceDictionary) - { - instanceOfGenericIDictionary.Add((string)entry.Key, (TRuntimeProperty)entry.Value); - } - return instanceOfGenericIDictionary; - } + return state.Current.JsonPropertyInfo.DeclaredClassInfo.CreateObject(); } - */ + } + + private JsonDictionaryConverterState.DictionaryBuilder CreateDictionaryBuilderInstance(Type dictionaryType, object instance, JsonSerializerOptions options) + { + JsonDictionaryConverterState.DictionaryBuilderConstructorDelegate ctor = FindCachedDictionaryBuilderCtor(dictionaryType, options); + Debug.Assert(ctor != null); + return ctor(instance); + } - ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection( - state.Current.JsonPropertyInfo.DeclaredPropertyType, - state.Current.JsonPropertyInfo.ParentClassType, - state.Current.JsonPropertyInfo.PropertyInfo); - return null; + private JsonClassInfo.ConstructorDelegate FindCachedCtor(Type type, JsonSerializerOptions options) + { + return s_ctors.GetOrAdd(type.FullName, _ => options.MemberAccessorStrategy.CreateConstructor(type)); + } + + private JsonDictionaryConverterState.DictionaryBuilderConstructorDelegate FindCachedDictionaryBuilderCtor(Type dictionaryType, JsonSerializerOptions options) + { + return s_dictionaryBuilderCtors.GetOrAdd(dictionaryType.FullName, _ => options.MemberAccessorStrategy.CreateDictionaryBuilderConstructor(dictionaryType)); } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs index 8d33b00abea9..446d063da23e 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Generic; +using System.Collections.Concurrent; using System.Collections.ObjectModel; using System.Diagnostics; @@ -12,8 +13,8 @@ namespace System.Text.Json.Serialization.Converters internal sealed class DefaultDerivedEnumerableConverter : JsonEnumerableConverter { // Cache constructors for performance. - private static readonly Dictionary s_ctors = new Dictionary(); - private static readonly Dictionary s_collectonBuilderCtors = new Dictionary(); + private static readonly ConcurrentDictionary s_ctors = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary s_collectonBuilderCtors = new ConcurrentDictionary(); public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) { @@ -62,32 +63,41 @@ public override void BeginEnumerable(ref ReadStack state, JsonSerializerOptions state.Current.EnumerableConverterState = new JsonEnumerableConverterState { - FinalCollection = CreateCollectionBuilderInstance(collectionType, instance, options) + Builder = CreateCollectionBuilderInstance(collectionType, instance, options) }; } } - public override void AddItemToEnumerable(ref ReadStack state, JsonSerializerOptions options, object value) + public override void AddItemToEnumerable(ref ReadStack state, JsonSerializerOptions options, ref T value) { Debug.Assert(state.Current.EnumerableConverterState?.FinalList != null || - state.Current.EnumerableConverterState.FinalCollection != null); + state.Current.EnumerableConverterState.Builder != null); - if (state.Current.EnumerableConverterState.FinalList != null) + IList finalList = state.Current.EnumerableConverterState.FinalList; + + if (finalList != null) { - state.Current.EnumerableConverterState.FinalList.Add(value); + if (finalList is IList typedList) + { + typedList.Add(value); + } + else + { + finalList.Add(value); + } } else { - state.Current.EnumerableConverterState.FinalCollection.Add(value); + state.Current.EnumerableConverterState.Builder.Add(ref value); } } public override object EndEnumerable(ref ReadStack state, JsonSerializerOptions options) { Debug.Assert(state.Current.EnumerableConverterState?.FinalList != null || - state.Current.EnumerableConverterState.FinalCollection != null); + state.Current.EnumerableConverterState.Builder != null); - return state.Current.EnumerableConverterState.FinalList ?? state.Current.EnumerableConverterState.FinalCollection.Instance; + return state.Current.EnumerableConverterState.FinalList ?? state.Current.EnumerableConverterState.Builder.Instance; } private object CreateConcreteInstance(ref ReadStack state, JsonSerializerOptions options) @@ -116,28 +126,12 @@ private JsonEnumerableConverterState.CollectionBuilder CreateCollectionBuilderIn private JsonClassInfo.ConstructorDelegate FindCachedCtor(Type type, JsonSerializerOptions options) { - string key = type.FullName; - - if (!s_ctors.TryGetValue(key, out JsonClassInfo.ConstructorDelegate ctor)) - { - ctor = options.MemberAccessorStrategy.CreateConstructor(type); - s_ctors[key] = ctor; - } - - return ctor; + return s_ctors.GetOrAdd(type.FullName, _ => options.MemberAccessorStrategy.CreateConstructor(type)); } private JsonEnumerableConverterState.CollectionBuilderConstructorDelegate FindCachedCollectionBuilderCtor(Type collectionType, JsonSerializerOptions options) { - string key = collectionType.FullName; - - if (!s_collectonBuilderCtors.TryGetValue(key, out JsonEnumerableConverterState.CollectionBuilderConstructorDelegate ctor)) - { - ctor = options.MemberAccessorStrategy.CreateCollectionBuilderConstructor(collectionType); - s_collectonBuilderCtors[key] = ctor; - } - - return ctor; + return s_collectonBuilderCtors.GetOrAdd(collectionType.FullName, _ => options.MemberAccessorStrategy.CreateCollectionBuilderConstructor(collectionType)); } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs index 0ed3f45cc60a..0519afa77256 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections; -using System.Collections.Generic; +using System.Collections.Concurrent; using System.Collections.ObjectModel; using System.Diagnostics; @@ -12,7 +12,7 @@ namespace System.Text.Json.Serialization.Converters internal sealed class DefaultICollectionConverter : JsonTemporaryListConverter { // Cache factories for performance. - private static readonly Dictionary s_factories = new Dictionary(); + private static readonly ConcurrentDictionary s_factories = new ConcurrentDictionary(); public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) { @@ -59,63 +59,26 @@ public override object EndEnumerable(ref ReadStack state, JsonSerializerOptions Debug.Assert(state.Current.EnumerableConverterState?.TemporaryList != null); IList sourceList = state.Current.EnumerableConverterState.TemporaryList; - Type collectionType = state.Current.JsonPropertyInfo.RuntimePropertyType; - try - { - if (collectionType.IsGenericType) - { - switch (collectionType.GetGenericTypeDefinition().FullName) - { - case JsonClassInfo.ReadOnlyCollectionGenericTypeName: - case JsonClassInfo.ReadOnlyObservableCollectionGenericTypeName: - case JsonClassInfo.StackGenericTypeName: - case JsonClassInfo.QueueGenericTypeName: - case JsonClassInfo.SortedSetGenericTypeName: - return CreateEnumerableInstance( - collectionType, - sourceList, - options); - } - } - else - { - if (collectionType == typeof(ArrayList)) - { - return new ArrayList(sourceList); - } + JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; + Type collectionType = jsonPropertyInfo.RuntimePropertyType; + Type implementedCollectionType = jsonPropertyInfo.ImplementedCollectionPropertyType; - switch (collectionType.FullName) - { - case JsonClassInfo.StackTypeName: - case JsonClassInfo.QueueTypeName: - return CreateEnumerableInstance( - collectionType, - sourceList, - options); - } - } - - return Activator.CreateInstance(collectionType, state.Current.EnumerableConverterState.TemporaryList); - } - catch (MissingMethodException) + if (implementedCollectionType == typeof(ArrayList)) { - ThrowHelper.ThrowNotSupportedException_DeserializeInstanceConstructorOfTypeNotFound(state.Current.JsonPropertyInfo.DeclaredPropertyType, state.Current.EnumerableConverterState.TemporaryList.GetType()); - return null; + return new ArrayList(sourceList); } + + return CreateEnumerableInstance(collectionType, sourceList, options); } private object CreateEnumerableInstance(Type collectionType, IList temporaryList, JsonSerializerOptions options) { Debug.Assert(collectionType != null); - string key = collectionType.FullName; - - if (!s_factories.TryGetValue(key, out JsonEnumerableConverterState.WrappedEnumerableFactory factory)) - { - factory = options.MemberAccessorStrategy.CreateWrappedEnumerableFactoryConstructor(collectionType, temporaryList.GetType())(options); - s_factories[key] = factory; - } + JsonEnumerableConverterState.WrappedEnumerableFactory factory = + s_factories.GetOrAdd(collectionType.FullName, _ => + options.MemberAccessorStrategy.CreateWrappedEnumerableFactoryConstructor(collectionType, temporaryList.GetType())(options)); return factory.CreateFromList(temporaryList); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs index 275a2cb2f5a8..8f22fbc2ece7 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs @@ -3,38 +3,69 @@ // See the LICENSE file in the project root for more information. using System.Collections; +using System.Collections.Concurrent; using System.Diagnostics; namespace System.Text.Json.Serialization.Converters { internal sealed class DefaultIDictionaryConverter : JsonTemporaryDictionaryConverter { + // Cache factories for performance. + private static readonly ConcurrentDictionary s_factories = new ConcurrentDictionary(); + public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) { - throw new NotImplementedException(); + return JsonClassInfo.IsDeserializedByConstructingWithIDictionary(implementedCollectionType); } - public override object EndDictionary(ref ReadStack state, JsonSerializerOptions options) + public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) { - Debug.Assert(state.Current.DictionaryConverterState?.TemporaryDictionary != null); + Type implementedCollectionPropertyType = jsonPropertyInfo.ImplementedCollectionPropertyType; - // Note: Types are defined explicityly here for performance. - try + if (implementedCollectionPropertyType.IsInterface) { - /*if (parentType.FullName == JsonClassInfo.HashtableTypeName) + if (implementedCollectionPropertyType.IsGenericType) { - return new Hashtable(sourceDictionary); - }*/ + switch (implementedCollectionPropertyType.GetGenericTypeDefinition().FullName) + { + case JsonClassInfo.ReadOnlyDictionaryGenericInterfaceTypeName: + return implementedCollectionPropertyType.Assembly.GetType(JsonClassInfo.ReadOnlyDictionaryGenericTypeName).MakeGenericType(typeof(string), jsonPropertyInfo.CollectionElementType); + } + } - // ReadOnlyDictionary<,> would require a reference to System.ObjectModel - - return (IDictionary)Activator.CreateInstance(state.Current.JsonPropertyInfo.DeclaredPropertyType, state.Current.DictionaryConverterState.TemporaryDictionary); + ThrowHelper.ThrowInvalidOperationException_DeserializePolymorphicInterface(implementedCollectionPropertyType); } - catch (MissingMethodException) + + return jsonPropertyInfo.DeclaredPropertyType; + } + + public override object EndDictionary(ref ReadStack state, JsonSerializerOptions options) + { + Debug.Assert(state.Current.DictionaryConverterState?.TemporaryDictionary != null); + + IDictionary sourceDictionary = state.Current.DictionaryConverterState.TemporaryDictionary; + + JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; + Type dictionaryType = jsonPropertyInfo.RuntimePropertyType; + Type implementedCollectionType = jsonPropertyInfo.ImplementedCollectionPropertyType; + + if (implementedCollectionType == typeof(Hashtable)) { - ThrowHelper.ThrowNotSupportedException_DeserializeInstanceConstructorOfTypeNotFound(state.Current.JsonPropertyInfo.DeclaredPropertyType, state.Current.DictionaryConverterState.TemporaryDictionary.GetType()); - return null; + return new Hashtable(sourceDictionary); } + + return CreateDictionaryInstance(dictionaryType, sourceDictionary, options); + } + + private object CreateDictionaryInstance(Type dictionaryType, IDictionary temporaryDictionary, JsonSerializerOptions options) + { + Debug.Assert(dictionaryType != null); + + JsonDictionaryConverterState.WrappedDictionaryFactory factory = + s_factories.GetOrAdd(dictionaryType.FullName, _ => + options.MemberAccessorStrategy.CreateWrappedDictionaryFactoryConstructor(dictionaryType, temporaryDictionary.GetType())(options)); + + return factory.CreateFromDictionary(temporaryDictionary); } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs index 3eeefd94a783..702af7fe09df 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs @@ -56,18 +56,6 @@ public static bool IsImmutableDictionary(Type type) } } - public override object EndDictionary(ref ReadStack state, JsonSerializerOptions options) - { - Debug.Assert(state.Current.DictionaryConverterState?.TemporaryDictionary != null); - - Type immutableCollectionType = state.Current.JsonPropertyInfo.RuntimePropertyType; - Type elementType = state.Current.GetElementType(); - - string delegateKey = DefaultImmutableEnumerableConverter.GetDelegateKey(immutableCollectionType, elementType, out _, out _); - - return CreateImmutableDictionaryInstance(ref state, immutableCollectionType, delegateKey, state.Current.DictionaryConverterState.TemporaryDictionary, options); - } - public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) { return implementedCollectionType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName); @@ -78,6 +66,18 @@ public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) return jsonPropertyInfo.DeclaredPropertyType; } + public override object EndDictionary(ref ReadStack state, JsonSerializerOptions options) + { + Debug.Assert(state.Current.DictionaryConverterState?.TemporaryDictionary != null); + + Type immutableCollectionType = state.Current.JsonPropertyInfo.RuntimePropertyType; + Type collectionElementType = state.Current.JsonPropertyInfo.CollectionElementType; + + string delegateKey = DefaultImmutableEnumerableConverter.GetDelegateKey(immutableCollectionType, collectionElementType, out _, out _); + + return CreateImmutableDictionaryInstance(ref state, immutableCollectionType, delegateKey, state.Current.DictionaryConverterState.TemporaryDictionary, options); + } + // Creates an IEnumerable and populates it with the items in the // sourceList argument then uses the delegateKey argument to identify the appropriate cached // CreateRange method to create and return the desired immutable collection type. diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs index 288797b7637e..00965335c305 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Collections; -using System.Collections.Generic; using System.Diagnostics; namespace System.Text.Json.Serialization.Converters diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs index d8870ba2b641..6832d31c9cb3 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs @@ -400,7 +400,11 @@ public static ClassType GetClassType(Type declaredType, Type implementedCollecti { Debug.Assert(declaredType != null); - if (implementedCollectionType.IsGenericType && implementedCollectionType.GetGenericTypeDefinition() == typeof(Nullable<>)) + Type genericTypeDefinition = !implementedCollectionType.IsGenericType + ? null + : implementedCollectionType.GetGenericTypeDefinition(); + + if (genericTypeDefinition == typeof(Nullable<>)) { implementedCollectionType = Nullable.GetUnderlyingType(implementedCollectionType); } @@ -415,7 +419,8 @@ public static ClassType GetClassType(Type declaredType, Type implementedCollecti return ClassType.Value; } - if (typeof(IDictionary).IsAssignableFrom(implementedCollectionType)) + if (typeof(IDictionary).IsAssignableFrom(implementedCollectionType) || + (genericTypeDefinition != null && typeof(IDictionary<,>).IsAssignableFrom(genericTypeDefinition))) { return ClassType.Dictionary; } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs index c1eebaf1e6b1..dfaaf043f470 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; @@ -10,18 +11,85 @@ namespace System.Text.Json.Serialization.Converters { internal class JsonDictionaryConverterState { + public delegate DictionaryBuilder DictionaryBuilderConstructorDelegate(object instance); + public delegate WrappedDictionaryFactory WrappedDictionaryFactoryConstructorDelegate(JsonSerializerOptions options); + public delegate object DictionaryConstructorDelegate(TSourceDictionary sourceDictionary) where TSourceDictionary : IDictionary; + + public abstract class DictionaryBuilder + { + public abstract object Instance { get; } + public abstract int Count { get; } + + public abstract void Add(string key, ref TPropertyType item); + } + + public sealed class DictionaryBuilder : DictionaryBuilder + { + private readonly IDictionary _instance; + + public override object Instance => _instance; + public override int Count => _instance.Count; + + public DictionaryBuilder(object instance) + { + Debug.Assert(instance != null && instance is IDictionary); + _instance = (IDictionary)instance; + } + + public override void Add(string key, ref TPropertyType item) + { + Debug.Assert(!string.IsNullOrEmpty(key)); + Debug.Assert(item == null || item.GetType() == typeof(T)); + + ((IDictionary)_instance).Add(key, item); + } + } + + public abstract class WrappedDictionaryFactory + { + public abstract object CreateFromDictionary(IDictionary sourceDictionary); + } + + public sealed class WrappedDictionaryFactory : WrappedDictionaryFactory + where TDictionary : IDictionary + where TSourceDictionary : IDictionary + { + private readonly DictionaryConstructorDelegate _ctor; + + public WrappedDictionaryFactory(JsonSerializerOptions options) + { + Debug.Assert(options != null); + + _ctor = options.MemberAccessorStrategy.CreateDictionaryConstructor(); + } + + public override object CreateFromDictionary(IDictionary sourceDictionary) + { + Debug.Assert(sourceDictionary != null && sourceDictionary is TSourceDictionary); + + if (_ctor == null) + { + ThrowHelper.ThrowNotSupportedException_DeserializeInstanceConstructorOfTypeNotFound(typeof(TDictionary), sourceDictionary.GetType()); + } + + return _ctor((TSourceDictionary)sourceDictionary); + } + } + public IDictionary TemporaryDictionary; - public IDictionary FinalInstance; + public IDictionary FinalDictionary; + public DictionaryBuilder Builder; + + public int? Count => + FinalDictionary?.Count ?? + Builder?.Count ?? + TemporaryDictionary?.Count; } internal abstract class JsonTemporaryDictionaryConverter : JsonDictionaryConverter { - public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) - { - // Should runtimetype be something else? - - return typeof(Dictionary<,>).MakeGenericType(typeof(string), jsonPropertyInfo.CollectionElementType); - } + // Cache concrete dictionary constructors for performance. + private static readonly ConcurrentDictionary s_ctors = new ConcurrentDictionary(); public override void BeginDictionary(ref ReadStack state, JsonSerializerOptions options) { @@ -29,15 +97,35 @@ public override void BeginDictionary(ref ReadStack state, JsonSerializerOptions state.Current.DictionaryConverterState = new JsonDictionaryConverterState { - TemporaryDictionary = (IDictionary)state.Current.JsonPropertyInfo.RuntimeClassInfo.CreateObject() + TemporaryDictionary = CreateConcreteDictionary(state.Current.JsonPropertyInfo, options) }; } - public override void AddItemToDictionary(ref ReadStack state, JsonSerializerOptions options, string key, object value) + public override void AddItemToDictionary(ref ReadStack state, JsonSerializerOptions options, string key, ref T value) { Debug.Assert(state.Current.DictionaryConverterState?.TemporaryDictionary != null); - state.Current.DictionaryConverterState.TemporaryDictionary.Add(key, value); + ((IDictionary)state.Current.DictionaryConverterState.TemporaryDictionary).Add(key, value); + } + + protected virtual Type ResolveTemporaryDictionaryType(JsonPropertyInfo jsonPropertyInfo) + => typeof(Dictionary<,>); + + private IDictionary CreateConcreteDictionary(JsonPropertyInfo jsonPropertyInfo, JsonSerializerOptions options) + { + Debug.Assert(jsonPropertyInfo?.CollectionElementType != null); + + Type temporaryDictionaryType = ResolveTemporaryDictionaryType(jsonPropertyInfo); + Type collectionElementType = jsonPropertyInfo.CollectionElementType; + + string key = $"{temporaryDictionaryType.FullName}[{collectionElementType.FullName}]"; + + JsonClassInfo.ConstructorDelegate ctor = s_ctors.GetOrAdd(key, () => + { + return options.MemberAccessorStrategy.CreateConstructor(temporaryDictionaryType.MakeGenericType(typeof(string), collectionElementType)); + }); + + return (IDictionary)ctor(); } } @@ -52,7 +140,7 @@ internal abstract class JsonDictionaryConverter public abstract bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType); public abstract Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo); public abstract void BeginDictionary(ref ReadStack state, JsonSerializerOptions options); - public abstract void AddItemToDictionary(ref ReadStack state, JsonSerializerOptions options, string key, object value); + public abstract void AddItemToDictionary(ref ReadStack state, JsonSerializerOptions options, string key, ref T value); public abstract object EndDictionary(ref ReadStack state, JsonSerializerOptions options); } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs index b2ef09f0726a..0e0ce80f6637 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; @@ -17,8 +18,9 @@ internal class JsonEnumerableConverterState public abstract class CollectionBuilder { public abstract object Instance { get; } + public abstract int Count { get; } - public abstract void Add(object item); + public abstract void Add(ref TPropertyType item); } public sealed class CollectionBuilder : CollectionBuilder @@ -26,6 +28,7 @@ public sealed class CollectionBuilder : CollectionBuilder private readonly ICollection _instance; public override object Instance => _instance; + public override int Count => _instance.Count; public CollectionBuilder(object instance) { @@ -33,10 +36,11 @@ public CollectionBuilder(object instance) _instance = (ICollection)instance; } - public override void Add(object item) + public override void Add(ref TPropertyType item) { Debug.Assert(item == null || item.GetType() == typeof(T)); - _instance.Add((T)item); + + ((ICollection)_instance).Add(item); } } @@ -73,13 +77,18 @@ public override object CreateFromList(IEnumerable sourceList) public IList TemporaryList; public IList FinalList; - public CollectionBuilder FinalCollection; + public CollectionBuilder Builder; + + public int? Count => + FinalList?.Count ?? + Builder?.Count ?? + TemporaryList?.Count; } internal abstract class JsonTemporaryListConverter : JsonEnumerableConverter { // Cache concrete list constructors for performance. - private static readonly Dictionary s_ctors = new Dictionary(); + private static readonly ConcurrentDictionary s_ctors = new ConcurrentDictionary(); public override void BeginEnumerable(ref ReadStack state, JsonSerializerOptions options) { @@ -91,11 +100,11 @@ public override void BeginEnumerable(ref ReadStack state, JsonSerializerOptions }; } - public override void AddItemToEnumerable(ref ReadStack state, JsonSerializerOptions options, object value) + public override void AddItemToEnumerable(ref ReadStack state, JsonSerializerOptions options, ref T value) { Debug.Assert(state.Current.EnumerableConverterState?.TemporaryList != null); - state.Current.EnumerableConverterState.TemporaryList.Add(value); + ((IList)state.Current.EnumerableConverterState.TemporaryList).Add(value); } protected virtual Type ResolveTemporaryListType(JsonPropertyInfo jsonPropertyInfo) @@ -105,15 +114,15 @@ private IList CreateConcreteList(JsonPropertyInfo jsonPropertyInfo, JsonSerializ { Debug.Assert(jsonPropertyInfo?.CollectionElementType != null); - Type TemporaryListType = ResolveTemporaryListType(jsonPropertyInfo).MakeGenericType(jsonPropertyInfo.CollectionElementType); + Type temporaryListType = ResolveTemporaryListType(jsonPropertyInfo); + Type collectionElementType = jsonPropertyInfo.CollectionElementType; - string key = TemporaryListType.FullName; + string key = $"{temporaryListType.FullName}[{collectionElementType.FullName}]"; - if (!s_ctors.TryGetValue(key, out JsonClassInfo.ConstructorDelegate ctor)) + JsonClassInfo.ConstructorDelegate ctor = s_ctors.GetOrAdd(key, () => { - ctor = options.MemberAccessorStrategy.CreateConstructor(TemporaryListType); - s_ctors[key] = ctor; - } + return options.MemberAccessorStrategy.CreateConstructor(temporaryListType.MakeGenericType(collectionElementType)); + }); return (IList)ctor(); } @@ -124,7 +133,7 @@ internal abstract class JsonEnumerableConverter public abstract bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType); public abstract Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo); public abstract void BeginEnumerable(ref ReadStack state, JsonSerializerOptions options); - public abstract void AddItemToEnumerable(ref ReadStack state, JsonSerializerOptions options, object value); + public abstract void AddItemToEnumerable(ref ReadStack state, JsonSerializerOptions options, ref T value); public abstract object EndEnumerable(ref ReadStack state, JsonSerializerOptions options); } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs index 11b8d19af092..52cc57d372c1 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs @@ -19,7 +19,7 @@ private static void HandleStartArray( if (state.Current.CollectionPropertyInitialized) { // A nested json array so push a new stack frame. - Type elementType = jsonPropertyInfo.CollectionElementClassInfo.Type; + Type elementType = jsonPropertyInfo.CollectionElementType; state.Push(); state.Current.Initialize(elementType, options); @@ -54,10 +54,11 @@ private static void HandleEndArray( { state.Pop(); - if (state.Current.JsonPropertyInfo.EnumerableConverter != null) - state.Current.JsonPropertyInfo.EnumerableConverter.AddItemToEnumerable(ref state, options, EnumerableInstance); - else - state.Current.ReturnValue = EnumerableInstance; + if (state.Current.IsProcessingEnumerableOrDictionary) + { + // Outer enumerable or dictionary. + ApplyValueToEnumerable(options, ref state, ref EnumerableInstance); + } } } @@ -71,14 +72,14 @@ internal static void ApplyValueToEnumerable( if (state.Current.IsProcessingEnumerable) { - state.Current.JsonPropertyInfo.EnumerableConverter.AddItemToEnumerable(ref state, options, value); + state.Current.JsonPropertyInfo.EnumerableConverter.AddItemToEnumerable(ref state, options, ref value); } else if (state.Current.IsProcessingDictionary) { string key = state.Current.KeyName; Debug.Assert(!string.IsNullOrEmpty(key)); - state.Current.JsonPropertyInfo.DictionaryConverter.AddItemToDictionary(ref state, options, key, value); + state.Current.JsonPropertyInfo.DictionaryConverter.AddItemToDictionary(ref state, options, key, ref value); } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs index a727059248d1..dcb0f858692c 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs @@ -2,22 +2,64 @@ // 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.Diagnostics; -using System.Text.Json.Serialization.Converters; namespace System.Text.Json { public static partial class JsonSerializer { - private static void HandleStartDictionary(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) + private static void HandleStartDictionary( + JsonSerializerOptions options, + ref ReadStack state) { - throw new NotImplementedException(); + Debug.Assert(state.Current.IsProcessingEnumerableOrDictionary); + + JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; + + if (state.Current.CollectionPropertyInitialized) + { + // A nested object or dictionary so push new frame. + Type elementType = jsonPropertyInfo.CollectionElementType; + + state.Push(); + state.Current.Initialize(elementType, options); + HandleEndDictionary(options, ref state); + return; + } + + state.Current.CollectionPropertyInitialized = true; + + Debug.Assert(jsonPropertyInfo?.DictionaryConverter != null); + + jsonPropertyInfo.DictionaryConverter.BeginDictionary(ref state, options); } - private static void HandleEndDictionary(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) + private static void HandleEndDictionary( + JsonSerializerOptions options, + ref ReadStack state) { - throw new NotImplementedException(); + Debug.Assert(state.Current.JsonPropertyInfo?.DictionaryConverter != null); + + object DictionaryInstance = state.Current.JsonPropertyInfo.DictionaryConverter.EndDictionary(ref state, options); + + state.Current.EndProperty(); + + if (state.IsLastFrame) + { + // Set the return value directly since this will be returned to the user. + state.Current.Reset(); + state.Current.ReturnValue = DictionaryInstance; + } + else + { + state.Pop(); + + if (state.Current.IsProcessingEnumerableOrDictionary) + { + // Outer enumerable or dictionary. + ApplyValueToEnumerable(options, ref state, ref DictionaryInstance); + } + } } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs index 3f55e16550c7..1430f0af88df 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs @@ -8,9 +8,90 @@ namespace System.Text.Json { 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) { - throw new NotImplementedException(); + if (state.Current.SkipProperty) + { + // Clear the current property in case it is a dictionary, since dictionaries must have EndProperty() called when completed. + // A non-dictionary property can also have EndProperty() called when completed, although it is redundant. + state.Current.EndProperty(); + + return false; + } + + JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; + + if (jsonPropertyInfo == null || (reader.CurrentDepth == 0 && jsonPropertyInfo.CanBeNull)) + { + Debug.Assert(state.IsLastFrame); + Debug.Assert(state.Current.ReturnValue == null); + return true; + } + + Debug.Assert(jsonPropertyInfo != null); + + if (state.Current.IsCollectionForClass) + { + AddNullToCollection(jsonPropertyInfo, ref reader, ref state, options); + return false; + } + + if (state.Current.IsCollectionForProperty) + { + if (state.Current.CollectionPropertyInitialized) + { + // Add the element. + AddNullToCollection(jsonPropertyInfo, ref reader, ref state, options); + } + else + { + // Reset so that `Is*Property` no longer returns true + state.Current.EndProperty(); + } + + return false; + } + + if (!jsonPropertyInfo.CanBeNull) + { + // Allow a value type converter to return a null value representation, such as JsonElement. + // Most likely this will throw JsonException. + jsonPropertyInfo.Read(JsonTokenType.Null, ref state, ref reader); + return false; + } + + if (state.Current.ReturnValue == null) + { + Debug.Assert(state.IsLastFrame); + return true; + } + + if (!jsonPropertyInfo.IgnoreNullValues) + { + state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value: null); + } + + return false; + } + + private static void AddNullToCollection(JsonPropertyInfo jsonPropertyInfo, ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options) + { + JsonPropertyInfo elementPropertyInfo = jsonPropertyInfo.CollectionElementClassInfo.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. + // Most likely this will throw JsonException unless the converter has special logic (like converter for JsonElement). + elementPropertyInfo.ReadEnumerable(JsonTokenType.Null, ref state, ref reader); + } + else + { + // Assume collection types are reference types and can have null assigned. + object value = null; + ApplyValueToEnumerable(options, ref state, ref value); + } } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs index 1420f2b82ee8..e6ea6ff65d4a 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs @@ -11,12 +11,53 @@ public static partial class JsonSerializer { private static void HandleStartObject(JsonSerializerOptions options, ref ReadStack state) { - throw new NotImplementedException(); + Debug.Assert(!state.Current.IsProcessingDictionary); + + if (state.Current.IsProcessingEnumerable) + { + // A nested object within an enumerable. + Type objType = state.Current.JsonPropertyInfo.CollectionElementType; + state.Push(); + state.Current.Initialize(objType, options); + } + else if (state.Current.JsonPropertyInfo != null) + { + // Nested object. + Type objType = state.Current.JsonPropertyInfo.RuntimePropertyType; + state.Push(); + state.Current.Initialize(objType, options); + } + + state.Current.ReturnValue = state.Current.JsonClassInfo.CreateObject(); } - private static void HandleEndObject(ref Utf8JsonReader reader, ref ReadStack state) + private static void HandleEndObject(JsonSerializerOptions options, ref ReadStack state) { - throw new NotImplementedException(); + // Only allow dictionaries to be processed here if this is the DataExtensionProperty. + 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) + { + state.Current.JsonClassInfo.UpdateSortedPropertyCache(ref state.Current); + } + + object value = state.Current.ReturnValue; + + if (state.IsLastFrame) + { + state.Current.Reset(); + state.Current.ReturnValue = value; + } + else + { + state.Pop(); + if (state.Current.IsProcessingEnumerableOrDictionary) + { + // Outer enumerable or dictionary. + ApplyValueToEnumerable(options, ref state, ref value); + } + } } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs index fffbfa36da51..bb7184eed090 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs @@ -70,7 +70,7 @@ private static void ReadCore( } else if (readStack.Current.IsProcessingDictionary) { - HandleStartDictionary(options, ref reader, ref readStack); + HandleStartDictionary(options, ref readStack); } else { @@ -89,11 +89,11 @@ private static void ReadCore( } else if (readStack.Current.IsProcessingDictionary) { - HandleEndDictionary(options, ref reader, ref readStack); + HandleEndDictionary(options, ref readStack); } else { - HandleEndObject(ref reader, ref readStack); + HandleEndObject(options, ref readStack); } } else if (tokenType == JsonTokenType.StartArray) @@ -126,7 +126,7 @@ private static void ReadCore( } else if (tokenType == JsonTokenType.Null) { - HandleNull(ref reader, ref readStack); + HandleNull(options, ref reader, ref readStack); } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs index 234d2a08f20f..54d12c574161 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs @@ -21,6 +21,14 @@ public abstract JsonEnumerableConverterState.EnumerableConstructorDelegate CreateDictionaryConstructor() + where TDictionary : IDictionary + where TSourceDictionary : IDictionary; + public abstract ImmutableCollectionCreator ImmutableCollectionCreateRange(Type constructingType, Type collectionType, Type elementType); public abstract ImmutableCollectionCreator ImmutableDictionaryCreateRange(Type constructingType, Type collectionType, Type elementType); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs index 5fb1119337f4..7fb8666ac220 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs @@ -2,7 +2,6 @@ // 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; @@ -86,11 +85,11 @@ private void AppendStackFrame(StringBuilder sb, in ReadStackFrame frame) else if (frame.IsProcessingEnumerable) { // For enumerables add the index. - IList list = frame.EnumerableConverterState.FinalList ?? frame.EnumerableConverterState.TemporaryList; - if (list != null) + int? collectionCount = frame.EnumerableConverterState.Count; + if (collectionCount.HasValue) { sb.Append(@"["); - sb.Append(list.Count); + sb.Append(collectionCount); sb.Append(@"]"); } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs index 86e02652010c..ed15fc48adb2 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs @@ -2,7 +2,6 @@ // 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.Runtime.CompilerServices; @@ -132,21 +131,6 @@ public void EndProperty() KeyName = null; } - public Type GetElementType() - { - if (IsCollectionForProperty) - { - return JsonPropertyInfo.CollectionElementType; - } - - if (IsCollectionForClass) - { - return JsonClassInfo.PolicyProperty.CollectionElementType; - } - - return JsonPropertyInfo.RuntimePropertyType; - } - public void SetReturnValue(object value) { Debug.Assert(ReturnValue == null); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs index e501ac8c33fa..fb17b8da2860 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs @@ -152,6 +152,102 @@ public override JsonEnumerableConverterState.EnumerableConstructorDelegate)); } + public override JsonDictionaryConverterState.DictionaryBuilderConstructorDelegate CreateDictionaryBuilderConstructor(Type dictionaryType) + { + Debug.Assert(dictionaryType != null); + + ConstructorInfo realMethod = dictionaryType.GetConstructor( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + binder: null, + new Type[] { typeof(object) }, + modifiers: null); + + if (realMethod == null) + { + return null; + } + + var dynamicMethod = new DynamicMethod( + ConstructorInfo.ConstructorName, + typeof(JsonDictionaryConverterState.DictionaryBuilder), + new Type[] { typeof(object) }, + typeof(ReflectionEmitMemberAccessor).Module, + skipVisibility: true); + + ILGenerator generator = dynamicMethod.GetILGenerator(); + + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Newobj, realMethod); + generator.Emit(OpCodes.Ret); + + return (JsonDictionaryConverterState.DictionaryBuilderConstructorDelegate)dynamicMethod.CreateDelegate( + typeof(JsonDictionaryConverterState.DictionaryBuilderConstructorDelegate)); + } + + public override JsonDictionaryConverterState.WrappedDictionaryFactoryConstructorDelegate CreateWrappedDictionaryFactoryConstructor(Type dictionaryType, Type sourceDictionaryType) + { + Debug.Assert(dictionaryType != null && sourceDictionaryType != null); + + Type factoryType = typeof(JsonDictionaryConverterState.WrappedDictionaryFactory<,>).MakeGenericType(dictionaryType, sourceDictionaryType); + + ConstructorInfo realMethod = factoryType.GetConstructor( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + binder: null, + new Type[] { typeof(JsonSerializerOptions) }, + modifiers: null); + + if (realMethod == null) + { + return null; + } + + var dynamicMethod = new DynamicMethod( + ConstructorInfo.ConstructorName, + typeof(JsonDictionaryConverterState.WrappedDictionaryFactory), + new Type[] { typeof(JsonSerializerOptions) }, + typeof(ReflectionEmitMemberAccessor).Module, + skipVisibility: true); + + ILGenerator generator = dynamicMethod.GetILGenerator(); + + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Newobj, realMethod); + generator.Emit(OpCodes.Ret); + + return (JsonDictionaryConverterState.WrappedDictionaryFactoryConstructorDelegate)dynamicMethod.CreateDelegate( + typeof(JsonDictionaryConverterState.WrappedDictionaryFactoryConstructorDelegate)); + } + + public override JsonDictionaryConverterState.DictionaryConstructorDelegate CreateDictionaryConstructor() + { + ConstructorInfo realMethod = typeof(TDictionary).GetConstructor( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + binder: null, + new Type[] { typeof(TSourceDictionary) }, + modifiers: null); + + if (realMethod == null) + { + return null; + } + + var dynamicMethod = new DynamicMethod( + ConstructorInfo.ConstructorName, + typeof(object), + new Type[] { typeof(TSourceDictionary) }, + typeof(ReflectionEmitMemberAccessor).Module, + skipVisibility: true); + + ILGenerator generator = dynamicMethod.GetILGenerator(); + + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Newobj, realMethod); + generator.Emit(OpCodes.Ret); + + return (JsonDictionaryConverterState.DictionaryConstructorDelegate)dynamicMethod.CreateDelegate( + typeof(JsonDictionaryConverterState.DictionaryConstructorDelegate)); + } + public override ImmutableCollectionCreator ImmutableCollectionCreateRange(Type constructingType, Type collectionType, Type elementType) { MethodInfo createRange = ImmutableCollectionCreateRangeMethod(constructingType, elementType); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs index a43ed181f24e..ba4c5ff4fbc2 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs @@ -82,6 +82,24 @@ public override JsonEnumerableConverterState.WrappedEnumerableFactoryConstructor return (JsonSerializerOptions options) => (JsonEnumerableConverterState.WrappedEnumerableFactory)Activator.CreateInstance(factoryType, options); } + public override JsonDictionaryConverterState.DictionaryBuilderConstructorDelegate CreateDictionaryBuilderConstructor(Type dictionaryType) + { + Debug.Assert(dictionaryType != null); + + ConstructorInfo realMethod = dictionaryType.GetConstructor( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + binder: null, + new Type[] { typeof(object) }, + modifiers: null); + + if (realMethod == null) + { + return null; + } + + return (object instance) => (JsonDictionaryConverterState.DictionaryBuilder)Activator.CreateInstance(dictionaryType, instance); + } + public override JsonEnumerableConverterState.EnumerableConstructorDelegate CreateEnumerableConstructor() { ConstructorInfo realMethod = typeof(TCollection).GetConstructor( @@ -98,6 +116,42 @@ public override JsonEnumerableConverterState.EnumerableConstructorDelegate (JsonEnumerableConverterState.WrappedEnumerableFactory)Activator.CreateInstance(typeof(TCollection), sourceList); } + public override JsonDictionaryConverterState.WrappedDictionaryFactoryConstructorDelegate CreateWrappedDictionaryFactoryConstructor(Type dictionaryType, Type sourceDictionaryType) + { + Debug.Assert(dictionaryType != null && sourceDictionaryType != null); + + Type factoryType = typeof(JsonDictionaryConverterState.WrappedDictionaryFactory<,>).MakeGenericType(dictionaryType, sourceDictionaryType); + + ConstructorInfo realMethod = factoryType.GetConstructor( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + binder: null, + new Type[] { typeof(JsonSerializerOptions) }, + modifiers: null); + + if (realMethod == null) + { + return null; + } + + return (JsonSerializerOptions options) => (JsonDictionaryConverterState.WrappedDictionaryFactory)Activator.CreateInstance(factoryType, options); + } + + public override JsonDictionaryConverterState.DictionaryConstructorDelegate CreateDictionaryConstructor() + { + ConstructorInfo realMethod = typeof(TDictionary).GetConstructor( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + binder: null, + new Type[] { typeof(TSourceDictionary) }, + modifiers: null); + + if (realMethod == null) + { + return null; + } + + return (TSourceDictionary sourceDictionary) => (JsonEnumerableConverterState.WrappedEnumerableFactory)Activator.CreateInstance(typeof(TDictionary), sourceDictionary); + } + public override ImmutableCollectionCreator ImmutableCollectionCreateRange(Type constructingType, Type collectionType, Type elementType) { MethodInfo createRange = ImmutableCollectionCreateRangeMethod(constructingType, elementType); From 4d0e57d9d152234b1e5e8c3db2ea457221917fff Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Sat, 14 Sep 2019 12:20:17 -0700 Subject: [PATCH 06/15] Fixes so far to get all the tests passing. --- .../DefaultDerivedEnumerableConverter.cs | 13 ++--- .../Serialization/JsonClassInfo.Helpers.cs | 1 + .../Text/Json/Serialization/JsonClassInfo.cs | 6 ++- .../Serialization/JsonDictionaryConverter.cs | 2 +- .../Serialization/JsonEnumerableConverter.cs | 20 ++++++-- .../JsonSerializer.Read.HandleArray.cs | 24 +++++---- .../JsonSerializer.Read.HandleDictionary.cs | 51 ++++++++++--------- .../JsonSerializer.Read.HandleObject.cs | 5 +- .../Json/Serialization/JsonSerializer.Read.cs | 22 ++++++-- .../ReflectionEmitMemberAccessor.cs | 5 +- .../Serialization/ReflectionMemberAccessor.cs | 4 +- 11 files changed, 97 insertions(+), 56 deletions(-) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs index 446d063da23e..a3cb3aa6b352 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs @@ -18,8 +18,7 @@ internal sealed class DefaultDerivedEnumerableConverter : JsonEnumerableConverte public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) { - return typeof(IList).IsAssignableFrom(implementedCollectionType) || - (implementedCollectionType.IsGenericType && typeof(ICollection<>).MakeGenericType(collectionElementType).IsAssignableFrom(implementedCollectionType)); + return typeof(IEnumerable).IsAssignableFrom(implementedCollectionType); } public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) @@ -59,11 +58,13 @@ public override void BeginEnumerable(ref ReadStack state, JsonSerializerOptions } else { - Type collectionType = typeof(JsonEnumerableConverterState.CollectionBuilder<>).MakeGenericType(state.Current.JsonPropertyInfo.CollectionElementType); + JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; + + Type collectionType = typeof(JsonEnumerableConverterState.CollectionBuilder<>).MakeGenericType(jsonPropertyInfo.CollectionElementType); state.Current.EnumerableConverterState = new JsonEnumerableConverterState { - Builder = CreateCollectionBuilderInstance(collectionType, instance, options) + Builder = CreateCollectionBuilderInstance(collectionType, jsonPropertyInfo, instance, options) }; } } @@ -117,11 +118,11 @@ private object CreateConcreteInstance(ref ReadStack state, JsonSerializerOptions } } - private JsonEnumerableConverterState.CollectionBuilder CreateCollectionBuilderInstance(Type collectionType, object instance, JsonSerializerOptions options) + private JsonEnumerableConverterState.CollectionBuilder CreateCollectionBuilderInstance(Type collectionType, JsonPropertyInfo source, object instance, JsonSerializerOptions options) { JsonEnumerableConverterState.CollectionBuilderConstructorDelegate ctor = FindCachedCollectionBuilderCtor(collectionType, options); Debug.Assert(ctor != null); - return ctor(instance); + return ctor(source, instance); } private JsonClassInfo.ConstructorDelegate FindCachedCtor(Type type, JsonSerializerOptions options) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs index ca8e36171000..6f3068b0953a 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs @@ -244,6 +244,7 @@ public static bool IsDeserializedByConstructingWithIDictionary(Type type) { switch (type.GetGenericTypeDefinition().FullName) { + case ReadOnlyDictionaryGenericInterfaceTypeName: case ReadOnlyDictionaryGenericTypeName: case SortedDictionaryGenericTypeName: return true; diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs index 6832d31c9cb3..2069d1c6b141 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs @@ -403,6 +403,9 @@ public static ClassType GetClassType(Type declaredType, Type implementedCollecti Type genericTypeDefinition = !implementedCollectionType.IsGenericType ? null : implementedCollectionType.GetGenericTypeDefinition(); + Type[] genericTypeArguments = !implementedCollectionType.IsGenericType + ? null + : implementedCollectionType.GenericTypeArguments; if (genericTypeDefinition == typeof(Nullable<>)) { @@ -420,7 +423,8 @@ public static ClassType GetClassType(Type declaredType, Type implementedCollecti } if (typeof(IDictionary).IsAssignableFrom(implementedCollectionType) || - (genericTypeDefinition != null && typeof(IDictionary<,>).IsAssignableFrom(genericTypeDefinition))) + (genericTypeDefinition != null && genericTypeArguments.Length >= 2 && + typeof(IEnumerable<>).MakeGenericType(typeof(KeyValuePair<,>).MakeGenericType(genericTypeArguments[0], genericTypeArguments[1])).IsAssignableFrom(implementedCollectionType))) { return ClassType.Dictionary; } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs index dfaaf043f470..38d766a821d6 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs @@ -120,7 +120,7 @@ private IDictionary CreateConcreteDictionary(JsonPropertyInfo jsonPropertyInfo, string key = $"{temporaryDictionaryType.FullName}[{collectionElementType.FullName}]"; - JsonClassInfo.ConstructorDelegate ctor = s_ctors.GetOrAdd(key, () => + JsonClassInfo.ConstructorDelegate ctor = s_ctors.GetOrAdd(key, _ => { return options.MemberAccessorStrategy.CreateConstructor(temporaryDictionaryType.MakeGenericType(typeof(string), collectionElementType)); }); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs index 0e0ce80f6637..22e4c8289003 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs @@ -11,7 +11,7 @@ namespace System.Text.Json.Serialization.Converters { internal class JsonEnumerableConverterState { - public delegate CollectionBuilder CollectionBuilderConstructorDelegate(object instance); + public delegate CollectionBuilder CollectionBuilderConstructorDelegate(JsonPropertyInfo source, object instance); public delegate WrappedEnumerableFactory WrappedEnumerableFactoryConstructorDelegate(JsonSerializerOptions options); public delegate object EnumerableConstructorDelegate(TSourceList sourceList) where TSourceList : IEnumerable; @@ -30,10 +30,20 @@ public sealed class CollectionBuilder : CollectionBuilder public override object Instance => _instance; public override int Count => _instance.Count; - public CollectionBuilder(object instance) + public CollectionBuilder(JsonPropertyInfo source, object instance) { - Debug.Assert(instance != null && instance is ICollection); - _instance = (ICollection)instance; + Debug.Assert(source != null && instance != null); + + if (instance is ICollection collectionInstance) + { + _instance = collectionInstance; + return; + } + + ThrowHelper.ThrowNotSupportedException_SerializationNotSupportedCollection( + source.DeclaredPropertyType, + source.ParentClassType, + source.PropertyInfo); } public override void Add(ref TPropertyType item) @@ -119,7 +129,7 @@ private IList CreateConcreteList(JsonPropertyInfo jsonPropertyInfo, JsonSerializ string key = $"{temporaryListType.FullName}[{collectionElementType.FullName}]"; - JsonClassInfo.ConstructorDelegate ctor = s_ctors.GetOrAdd(key, () => + JsonClassInfo.ConstructorDelegate ctor = s_ctors.GetOrAdd(key, _ => { return options.MemberAccessorStrategy.CreateConstructor(temporaryListType.MakeGenericType(collectionElementType)); }); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs index 52cc57d372c1..6dbb914bf099 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs @@ -42,20 +42,26 @@ private static void HandleEndArray( object EnumerableInstance = state.Current.JsonPropertyInfo.EnumerableConverter.EndEnumerable(ref state, options); - state.Current.EndProperty(); - - if (state.IsLastFrame) + if (state.Current.IsEnumerableProperty) { - // Set the return value directly since this will be returned to the user. - state.Current.Reset(); - state.Current.ReturnValue = EnumerableInstance; + // Set instance as property on currently building object. + state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, EnumerableInstance); + state.Current.EndProperty(); } else { - state.Pop(); - - if (state.Current.IsProcessingEnumerableOrDictionary) + if (state.IsLastFrame) + { + // Set the return value directly since this will be returned to the user. + state.Current.Reset(); + state.Current.ReturnValue = EnumerableInstance; + } + else { + state.Pop(); + + Debug.Assert(state.Current.IsProcessingEnumerableOrDictionary); + // Outer enumerable or dictionary. ApplyValueToEnumerable(options, ref state, ref EnumerableInstance); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs index dcb0f858692c..0e58e46122f6 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs @@ -14,24 +14,11 @@ private static void HandleStartDictionary( { Debug.Assert(state.Current.IsProcessingEnumerableOrDictionary); - JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; - - if (state.Current.CollectionPropertyInitialized) - { - // A nested object or dictionary so push new frame. - Type elementType = jsonPropertyInfo.CollectionElementType; - - state.Push(); - state.Current.Initialize(elementType, options); - HandleEndDictionary(options, ref state); - return; - } - state.Current.CollectionPropertyInitialized = true; - Debug.Assert(jsonPropertyInfo?.DictionaryConverter != null); + Debug.Assert(state.Current.JsonPropertyInfo?.DictionaryConverter != null); - jsonPropertyInfo.DictionaryConverter.BeginDictionary(ref state, options); + state.Current.JsonPropertyInfo.DictionaryConverter.BeginDictionary(ref state, options); } private static void HandleEndDictionary( @@ -42,20 +29,36 @@ private static void HandleEndDictionary( object DictionaryInstance = state.Current.JsonPropertyInfo.DictionaryConverter.EndDictionary(ref state, options); - state.Current.EndProperty(); - - if (state.IsLastFrame) + if (state.Current.IsDictionaryProperty) { - // Set the return value directly since this will be returned to the user. - state.Current.Reset(); - state.Current.ReturnValue = DictionaryInstance; + if (state.Current.JsonClassInfo.DataExtensionProperty == state.Current.JsonPropertyInfo) + { + // 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(). + HandleEndObject(options, ref state); + } + else + { + // Set instance as property on currently building object. + state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, DictionaryInstance); + state.Current.EndProperty(); + } } else { - state.Pop(); - - if (state.Current.IsProcessingEnumerableOrDictionary) + if (state.IsLastFrame) { + // Set the return value directly since this will be returned to the user. + state.Current.Reset(); + state.Current.ReturnValue = DictionaryInstance; + } + else + { + state.Pop(); + + Debug.Assert(state.Current.IsProcessingEnumerableOrDictionary); + // Outer enumerable or dictionary. ApplyValueToEnumerable(options, ref state, ref DictionaryInstance); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs index e6ea6ff65d4a..d2ac735d95af 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs @@ -2,7 +2,6 @@ // 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.Diagnostics; namespace System.Text.Json @@ -57,6 +56,10 @@ private static void HandleEndObject(JsonSerializerOptions options, ref ReadStack // Outer enumerable or dictionary. ApplyValueToEnumerable(options, ref state, ref value); } + else + { + state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); + } } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs index bb7184eed090..d9799544ca58 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs @@ -68,13 +68,25 @@ private static void ReadCore( break; } } - else if (readStack.Current.IsProcessingDictionary) - { - HandleStartDictionary(options, ref readStack); - } else { - HandleStartObject(options, ref readStack); + if (readStack.Current.CollectionPropertyInitialized) + { + // A nested object or dictionary so push new frame. + Type elementType = readStack.Current.JsonPropertyInfo.CollectionElementType; + + readStack.Push(); + readStack.Current.Initialize(elementType, options); + } + + if (readStack.Current.IsProcessingDictionary) + { + HandleStartDictionary(options, ref readStack); + } + else + { + HandleStartObject(options, ref readStack); + } } } else if (tokenType == JsonTokenType.EndObject) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs index fb17b8da2860..46174b997896 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs @@ -63,7 +63,7 @@ public override JsonEnumerableConverterState.CollectionBuilderConstructorDelegat ConstructorInfo realMethod = collectionType.GetConstructor( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, binder: null, - new Type[] { typeof(object) }, + new Type[] { typeof(JsonPropertyInfo), typeof(object) }, modifiers: null); if (realMethod == null) @@ -74,13 +74,14 @@ public override JsonEnumerableConverterState.CollectionBuilderConstructorDelegat var dynamicMethod = new DynamicMethod( ConstructorInfo.ConstructorName, typeof(JsonEnumerableConverterState.CollectionBuilder), - new Type[] { typeof(object) }, + new Type[] { typeof(JsonPropertyInfo), typeof(object) }, typeof(ReflectionEmitMemberAccessor).Module, skipVisibility: true); ILGenerator generator = dynamicMethod.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldarg_1); generator.Emit(OpCodes.Newobj, realMethod); generator.Emit(OpCodes.Ret); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs index ba4c5ff4fbc2..925a87567eca 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs @@ -51,7 +51,7 @@ public override JsonEnumerableConverterState.CollectionBuilderConstructorDelegat ConstructorInfo realMethod = collectionType.GetConstructor( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, binder: null, - new Type[] { typeof(object) }, + new Type[] { typeof(JsonPropertyInfo), typeof(object) }, modifiers: null); if (realMethod == null) @@ -59,7 +59,7 @@ public override JsonEnumerableConverterState.CollectionBuilderConstructorDelegat return null; } - return (object instance) => (JsonEnumerableConverterState.CollectionBuilder)Activator.CreateInstance(collectionType, instance); + return (JsonPropertyInfo source, object instance) => (JsonEnumerableConverterState.CollectionBuilder)Activator.CreateInstance(collectionType, source, instance); } public override JsonEnumerableConverterState.WrappedEnumerableFactoryConstructorDelegate CreateWrappedEnumerableFactoryConstructor(Type collectionType, Type sourceListType) From 2676ad8c703e46b9a1889613fec8fab717cecf0a Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Sun, 22 Sep 2019 19:09:36 -0700 Subject: [PATCH 07/15] Fixes to get tests passing. --- .../DefaultDerivedDictionaryConverter.cs | 4 +- .../DefaultDerivedEnumerableConverter.cs | 2 +- .../Converters/DefaultICollectionConverter.cs | 2 +- .../Converters/DefaultIDictionaryConverter.cs | 2 +- .../Serialization/JsonClassInfo.Helpers.cs | 10 +---- .../Serialization/JsonDictionaryConverter.cs | 20 +++++++++- .../Serialization/JsonEnumerableConverter.cs | 20 +++++++++- .../JsonSerializer.Read.HandleArray.cs | 37 ++++++++++++++++--- .../JsonSerializer.Read.HandleDictionary.cs | 10 +++-- .../Json/Serialization/JsonSerializer.Read.cs | 2 +- .../Text/Json/Serialization/ReadStack.cs | 2 +- .../Value.ReadTests.ImmutableCollections.cs | 8 +++- 12 files changed, 89 insertions(+), 30 deletions(-) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs index 60f11fa43570..435dbf3d65ff 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs @@ -66,11 +66,11 @@ public override void AddItemToDictionary(ref ReadStack state, JsonSerializerO { if (finalDictionary is IDictionary typedDictionary) { - typedDictionary.Add(key, value); + typedDictionary[key] = value; } else { - finalDictionary.Add(key, value); + finalDictionary[key] = value; } } else diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs index a3cb3aa6b352..6b45a84e0df5 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs @@ -25,7 +25,7 @@ public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) { Type implementedCollectionPropertyType = jsonPropertyInfo.ImplementedCollectionPropertyType; - if (implementedCollectionPropertyType.IsInterface) + if (jsonPropertyInfo.DeclaredPropertyType.IsInterface) { if (implementedCollectionPropertyType.IsGenericType) { diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs index 0519afa77256..0266834f8124 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs @@ -23,7 +23,7 @@ public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) { Type implementedCollectionPropertyType = jsonPropertyInfo.ImplementedCollectionPropertyType; - if (implementedCollectionPropertyType.IsInterface) + if (jsonPropertyInfo.DeclaredPropertyType.IsInterface) { if (implementedCollectionPropertyType.IsGenericType) { diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs index 8f22fbc2ece7..e0383e82066d 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs @@ -22,7 +22,7 @@ public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) { Type implementedCollectionPropertyType = jsonPropertyInfo.ImplementedCollectionPropertyType; - if (implementedCollectionPropertyType.IsInterface) + if (jsonPropertyInfo.DeclaredPropertyType.IsInterface) { if (implementedCollectionPropertyType.IsGenericType) { diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs index 6f3068b0953a..028d8d3bfcf0 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs @@ -220,7 +220,6 @@ public static bool IsDeserializedByConstructingWithIList(Type type) case ReadOnlyObservableCollectionGenericTypeName: case StackGenericTypeName: case QueueGenericTypeName: - case SortedSetGenericTypeName: return true; default: return false; @@ -246,20 +245,13 @@ public static bool IsDeserializedByConstructingWithIDictionary(Type type) { case ReadOnlyDictionaryGenericInterfaceTypeName: case ReadOnlyDictionaryGenericTypeName: - case SortedDictionaryGenericTypeName: return true; default: return false; } } - switch (type.FullName) - { - case SortedListTypeName: - return true; - default: - return false; - } + return false; } public static bool IsNativelySupportedCollection(Type queryType) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs index 38d766a821d6..c2121ebd84cf 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs @@ -41,7 +41,14 @@ public override void Add(string key, ref TPropertyType item) Debug.Assert(!string.IsNullOrEmpty(key)); Debug.Assert(item == null || item.GetType() == typeof(T)); - ((IDictionary)_instance).Add(key, item); + if (item is T typedItem) + { + _instance[key] = typedItem; + } + else + { + ((IDictionary)_instance)[key] = item; + } } } @@ -105,7 +112,16 @@ public override void AddItemToDictionary(ref ReadStack state, JsonSerializerO { Debug.Assert(state.Current.DictionaryConverterState?.TemporaryDictionary != null); - ((IDictionary)state.Current.DictionaryConverterState.TemporaryDictionary).Add(key, value); + IDictionary temporaryDictionary = state.Current.DictionaryConverterState.TemporaryDictionary; + + if (temporaryDictionary is IDictionary typedDictionary) + { + typedDictionary[key] = value; + } + else + { + temporaryDictionary[key] = value; + } } protected virtual Type ResolveTemporaryDictionaryType(JsonPropertyInfo jsonPropertyInfo) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs index 22e4c8289003..980659a0e11f 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs @@ -50,7 +50,14 @@ public override void Add(ref TPropertyType item) { Debug.Assert(item == null || item.GetType() == typeof(T)); - ((ICollection)_instance).Add(item); + if (item is T typedItem) + { + _instance.Add(typedItem); + } + else + { + ((ICollection)_instance).Add(item); + } } } @@ -114,7 +121,16 @@ public override void AddItemToEnumerable(ref ReadStack state, JsonSerializerO { Debug.Assert(state.Current.EnumerableConverterState?.TemporaryList != null); - ((IList)state.Current.EnumerableConverterState.TemporaryList).Add(value); + IList temporaryList = state.Current.EnumerableConverterState.TemporaryList; + + if (temporaryList is IList typedList) + { + typedList.Add(value); + } + else + { + temporaryList.Add(value); + } } protected virtual Type ResolveTemporaryListType(JsonPropertyInfo jsonPropertyInfo) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs index 6dbb914bf099..92fa3b7fd2b2 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs @@ -2,6 +2,8 @@ // 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; namespace System.Text.Json @@ -10,11 +12,24 @@ public static partial class JsonSerializer { private static void HandleStartArray( JsonSerializerOptions options, + ref Utf8JsonReader reader, ref ReadStack state) { - Debug.Assert(state.Current.IsProcessingEnumerableOrDictionary); - JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; + if (jsonPropertyInfo == null) + { + jsonPropertyInfo = state.Current.JsonClassInfo.CreateRootObject(options); + } + else if (state.Current.JsonClassInfo.ClassType == ClassType.Unknown) + { + jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, typeof(object), options); + } + + // Verify that we have a valid enumerable. + if (!state.Current.IsProcessingEnumerableOrDictionary) + { + ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(jsonPropertyInfo.RuntimePropertyType, reader, state.JsonPath()); + } if (state.Current.CollectionPropertyInitialized) { @@ -23,7 +38,7 @@ private static void HandleStartArray( state.Push(); state.Current.Initialize(elementType, options); - HandleStartArray(options, ref state); + HandleStartArray(options, ref reader, ref state); return; } @@ -76,16 +91,28 @@ internal static void ApplyValueToEnumerable( Debug.Assert(state.Current.JsonPropertyInfo != null); Debug.Assert(state.Current.IsProcessingEnumerableOrDictionary); + JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; + if (state.Current.IsProcessingEnumerable) { - state.Current.JsonPropertyInfo.EnumerableConverter.AddItemToEnumerable(ref state, options, ref value); + jsonPropertyInfo.EnumerableConverter.AddItemToEnumerable(ref state, options, ref value); } else if (state.Current.IsProcessingDictionary) { string key = state.Current.KeyName; Debug.Assert(!string.IsNullOrEmpty(key)); - state.Current.JsonPropertyInfo.DictionaryConverter.AddItemToDictionary(ref state, options, key, ref value); + if (state.Current.JsonClassInfo.DataExtensionProperty == jsonPropertyInfo) + { + Debug.Assert(state.Current.ReturnValue != null); + + IDictionary dictionary = (IDictionary)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue); + dictionary[key] = value; + } + else + { + jsonPropertyInfo.DictionaryConverter.AddItemToDictionary(ref state, options, key, ref value); + } } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs index 0e58e46122f6..b038bbeb43a5 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs @@ -27,11 +27,15 @@ private static void HandleEndDictionary( { Debug.Assert(state.Current.JsonPropertyInfo?.DictionaryConverter != null); - object DictionaryInstance = state.Current.JsonPropertyInfo.DictionaryConverter.EndDictionary(ref state, options); + JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; + + object DictionaryInstance = state.Current.JsonClassInfo.DataExtensionProperty == jsonPropertyInfo + ? null + : jsonPropertyInfo.DictionaryConverter.EndDictionary(ref state, options); if (state.Current.IsDictionaryProperty) { - if (state.Current.JsonClassInfo.DataExtensionProperty == state.Current.JsonPropertyInfo) + if (state.Current.JsonClassInfo.DataExtensionProperty == jsonPropertyInfo) { // 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 @@ -41,7 +45,7 @@ private static void HandleEndDictionary( else { // Set instance as property on currently building object. - state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, DictionaryInstance); + jsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, DictionaryInstance); state.Current.EndProperty(); } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs index d9799544ca58..ad9eed972113 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs @@ -117,7 +117,7 @@ private static void ReadCore( } else if (!readStack.Current.IsProcessingValue()) { - HandleStartArray(options, ref readStack); + HandleStartArray(options, ref reader, ref readStack); } else if (!HandleObjectAsValue(tokenType, options, ref reader, ref readStack, ref initialState, initialBytesConsumed)) { diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs index c8a4cc8d6284..a00f3167d762 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs @@ -82,7 +82,7 @@ private void AppendStackFrame(StringBuilder sb, in ReadStackFrame frame) else if (frame.IsProcessingEnumerable) { // For enumerables add the index. - int? collectionCount = frame.EnumerableConverterState.Count; + int? collectionCount = frame.EnumerableConverterState?.Count; if (collectionCount.HasValue) { sb.Append(@"["); diff --git a/src/System.Text.Json/tests/Serialization/Value.ReadTests.ImmutableCollections.cs b/src/System.Text.Json/tests/Serialization/Value.ReadTests.ImmutableCollections.cs index b02c1e0b995f..e6b8098cd614 100644 --- a/src/System.Text.Json/tests/Serialization/Value.ReadTests.ImmutableCollections.cs +++ b/src/System.Text.Json/tests/Serialization/Value.ReadTests.ImmutableCollections.cs @@ -339,8 +339,12 @@ public static void ReadPrimitiveIImmutableSetT() result = JsonSerializer.Deserialize>(Encoding.UTF8.GetBytes(@"[]")); Assert.Equal(0, result.Count()); - Assert.Throws(() => JsonSerializer.Deserialize(@"[""1"",""2""]")); - Assert.Throws(() => JsonSerializer.Deserialize(@"[]")); + StringIImmutableSetWrapper obj = JsonSerializer.Deserialize(@"[""1"",""2""]"); + Assert.Equal(2, obj.Count()); + + obj = JsonSerializer.Deserialize(@"[]"); + + Assert.Equal(0, obj.Count()); } [Fact] From b201dbe6ce7bd381e52f35e335f5c9c4a760cbe8 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Sun, 22 Sep 2019 20:37:40 -0700 Subject: [PATCH 08/15] More test fixup. --- .../Text/Json/Serialization/JsonDictionaryConverter.cs | 7 ++++++- .../Text/Json/Serialization/JsonPropertyInfoNullable.cs | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs index c2121ebd84cf..2fbc8252b1d3 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs @@ -39,12 +39,17 @@ public DictionaryBuilder(object instance) public override void Add(string key, ref TPropertyType item) { Debug.Assert(!string.IsNullOrEmpty(key)); - Debug.Assert(item == null || item.GetType() == typeof(T)); + Debug.Assert(item == null || typeof(T).IsAssignableFrom(item.GetType())); if (item is T typedItem) { _instance[key] = typedItem; } + else if (item == null) + { + // Handle null values for nullable types. + _instance[key] = default; + } else { ((IDictionary)_instance)[key] = item; diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs index 48930dba0557..97369bef9127 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs @@ -92,6 +92,11 @@ protected override void OnWriteDictionary(ref WriteStackFrame current, Utf8JsonW key = enumerator.Current.Key; value = enumerator.Current.Value; } + else if (current.CollectionEnumerator is IDictionaryEnumerator dictionaryEnumerator) + { + key = (string)dictionaryEnumerator.Key; + value = (TProperty?)dictionaryEnumerator.Value; + } Debug.Assert(key != null); From 58f6fecc4478d03aae764b485eda8c04b2535c79 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Sun, 22 Sep 2019 21:19:01 -0700 Subject: [PATCH 09/15] Test fixup. --- .../Converters/DefaultIDictionaryConverter.cs | 9 ++++++++- .../Text/Json/Serialization/JsonEnumerableConverter.cs | 2 +- .../System/Text/Json/Serialization/JsonPropertyInfo.cs | 8 ++------ .../Json/Serialization/ReflectionEmitMemberAccessor.cs | 10 +++++++++- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs index e0383e82066d..e7479143109c 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs @@ -63,7 +63,14 @@ private object CreateDictionaryInstance(Type dictionaryType, IDictionary tempora JsonDictionaryConverterState.WrappedDictionaryFactory factory = s_factories.GetOrAdd(dictionaryType.FullName, _ => - options.MemberAccessorStrategy.CreateWrappedDictionaryFactoryConstructor(dictionaryType, temporaryDictionary.GetType())(options)); + { + JsonDictionaryConverterState.WrappedDictionaryFactoryConstructorDelegate ctor = options.MemberAccessorStrategy.CreateWrappedDictionaryFactoryConstructor(dictionaryType, temporaryDictionary.GetType()); + if (ctor == null) + { + ThrowHelper.ThrowNotSupportedException_DeserializeInstanceConstructorOfTypeNotFound(dictionaryType, temporaryDictionary.GetType()); + } + return ctor(options); + }); return factory.CreateFromDictionary(temporaryDictionary); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs index 980659a0e11f..b301a24ee747 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs @@ -48,7 +48,7 @@ public CollectionBuilder(JsonPropertyInfo source, object instance) public override void Add(ref TPropertyType item) { - Debug.Assert(item == null || item.GetType() == typeof(T)); + Debug.Assert(item == null || typeof(T).IsAssignableFrom(item.GetType())); if (item is T typedItem) { diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs index 707ed8244ed1..7d835d2a9ead 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs @@ -175,6 +175,7 @@ public virtual void Initialize( ClassType = propertyClassType; ParentClassType = parentClassType; DeclaredPropertyType = declaredPropertyType; + RuntimePropertyType = DeclaredPropertyType; ImplementedCollectionPropertyType = implementedCollectionPropertyType; CollectionElementType = collectionElementType; PropertyInfo = propertyInfo; @@ -186,16 +187,11 @@ public virtual void Initialize( { ConverterBase = converter; } - - if (propertyClassType == ClassType.Enumerable || + else if (propertyClassType == ClassType.Enumerable || propertyClassType == ClassType.Dictionary) { DetermineEnumerableOrDictionaryConverter(); } - else - { - RuntimePropertyType = DeclaredPropertyType; - } } public abstract object GetValueAsObject(object obj); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs index 46174b997896..45e1b466908f 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs @@ -189,7 +189,15 @@ public override JsonDictionaryConverterState.WrappedDictionaryFactoryConstructor { Debug.Assert(dictionaryType != null && sourceDictionaryType != null); - Type factoryType = typeof(JsonDictionaryConverterState.WrappedDictionaryFactory<,>).MakeGenericType(dictionaryType, sourceDictionaryType); + Type factoryType; + try + { + factoryType = typeof(JsonDictionaryConverterState.WrappedDictionaryFactory<,>).MakeGenericType(dictionaryType, sourceDictionaryType); + } + catch (ArgumentException) + { + return null; + } ConstructorInfo realMethod = factoryType.GetConstructor( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, From 423ef21d579e39bc41d80eab6e82e7a95d7df7d9 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Sun, 22 Sep 2019 21:37:57 -0700 Subject: [PATCH 10/15] Test fixup. --- .../Text/Json/Serialization/JsonPropertyInfo.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs index 7d835d2a9ead..002a839b37c9 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs @@ -175,7 +175,6 @@ public virtual void Initialize( ClassType = propertyClassType; ParentClassType = parentClassType; DeclaredPropertyType = declaredPropertyType; - RuntimePropertyType = DeclaredPropertyType; ImplementedCollectionPropertyType = implementedCollectionPropertyType; CollectionElementType = collectionElementType; PropertyInfo = propertyInfo; @@ -186,9 +185,19 @@ public virtual void Initialize( if (converter != null) { ConverterBase = converter; + RuntimePropertyType = declaredPropertyType; + + // Avoid calling GetClassType since it will re-ask if there is a converter which is slow. + if (declaredPropertyType == typeof(object)) + { + ClassType = ClassType.Unknown; + } + else + { + ClassType = ClassType.Value; + } } - else if (propertyClassType == ClassType.Enumerable || - propertyClassType == ClassType.Dictionary) + else if (propertyClassType == ClassType.Enumerable || propertyClassType == ClassType.Dictionary) { DetermineEnumerableOrDictionaryConverter(); } From c41de95fdfee90eeeb2a45f510cb203a6876e7cc Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Sun, 22 Sep 2019 21:54:53 -0700 Subject: [PATCH 11/15] Test fixup. --- .../Converters/DefaultDerivedDictionaryConverter.cs | 2 +- .../Converters/DefaultDerivedEnumerableConverter.cs | 2 +- .../Json/Serialization/JsonDictionaryConverter.cs | 11 +++++++---- .../Json/Serialization/JsonEnumerableConverter.cs | 11 +++++++---- .../Json/Serialization/JsonPropertyInfoNotNullable.cs | 2 +- .../JsonPropertyInfoNotNullableContravariant.cs | 2 +- .../Json/Serialization/JsonPropertyInfoNullable.cs | 2 +- .../Serialization/JsonSerializer.Read.HandleArray.cs | 8 +++++--- .../JsonSerializer.Read.HandleDictionary.cs | 5 +++-- .../Serialization/JsonSerializer.Read.HandleNull.cs | 2 +- .../Serialization/JsonSerializer.Read.HandleObject.cs | 4 ++-- .../Text/Json/Serialization/JsonSerializer.Read.cs | 6 +++--- 12 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs index 435dbf3d65ff..cb539ed05f0b 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs @@ -55,7 +55,7 @@ public override void BeginDictionary(ref ReadStack state, JsonSerializerOptions } } - public override void AddItemToDictionary(ref ReadStack state, JsonSerializerOptions options, string key, ref T value) + public override void AddItemToDictionary(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options, string key, ref T value) { Debug.Assert(state.Current.DictionaryConverterState?.FinalDictionary != null || state.Current.DictionaryConverterState.Builder != null); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs index 6b45a84e0df5..fd4b6fc656d5 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs @@ -69,7 +69,7 @@ public override void BeginEnumerable(ref ReadStack state, JsonSerializerOptions } } - public override void AddItemToEnumerable(ref ReadStack state, JsonSerializerOptions options, ref T value) + public override void AddItemToEnumerable(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options, ref T value) { Debug.Assert(state.Current.EnumerableConverterState?.FinalList != null || state.Current.EnumerableConverterState.Builder != null); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs index 2fbc8252b1d3..214a1faff48f 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs @@ -113,11 +113,14 @@ public override void BeginDictionary(ref ReadStack state, JsonSerializerOptions }; } - public override void AddItemToDictionary(ref ReadStack state, JsonSerializerOptions options, string key, ref T value) + public override void AddItemToDictionary(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options, string key, ref T value) { - Debug.Assert(state.Current.DictionaryConverterState?.TemporaryDictionary != null); + IDictionary temporaryDictionary = state.Current.DictionaryConverterState?.TemporaryDictionary; - IDictionary temporaryDictionary = state.Current.DictionaryConverterState.TemporaryDictionary; + if (temporaryDictionary == null) + { + ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(value.GetType(), reader, state.JsonPath()); + } if (temporaryDictionary is IDictionary typedDictionary) { @@ -161,7 +164,7 @@ internal abstract class JsonDictionaryConverter public abstract bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType); public abstract Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo); public abstract void BeginDictionary(ref ReadStack state, JsonSerializerOptions options); - public abstract void AddItemToDictionary(ref ReadStack state, JsonSerializerOptions options, string key, ref T value); + public abstract void AddItemToDictionary(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options, string key, ref T value); public abstract object EndDictionary(ref ReadStack state, JsonSerializerOptions options); } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs index b301a24ee747..f55a9df1bc74 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs @@ -117,11 +117,14 @@ public override void BeginEnumerable(ref ReadStack state, JsonSerializerOptions }; } - public override void AddItemToEnumerable(ref ReadStack state, JsonSerializerOptions options, ref T value) + public override void AddItemToEnumerable(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options, ref T value) { - Debug.Assert(state.Current.EnumerableConverterState?.TemporaryList != null); + IList temporaryList = state.Current.EnumerableConverterState?.TemporaryList; - IList temporaryList = state.Current.EnumerableConverterState.TemporaryList; + if (temporaryList == null) + { + ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(value.GetType(), reader, state.JsonPath()); + } if (temporaryList is IList typedList) { @@ -159,7 +162,7 @@ internal abstract class JsonEnumerableConverter public abstract bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType); public abstract Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo); public abstract void BeginEnumerable(ref ReadStack state, JsonSerializerOptions options); - public abstract void AddItemToEnumerable(ref ReadStack state, JsonSerializerOptions options, ref T value); + public abstract void AddItemToEnumerable(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options, ref T value); public abstract object EndEnumerable(ref ReadStack state, JsonSerializerOptions options); } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs index cb14c117238e..2cc938247a11 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs @@ -54,7 +54,7 @@ protected override void OnReadEnumerable(JsonTokenType tokenType, ref ReadStack } TConverter value = Converter.Read(ref reader, RuntimePropertyType, Options); - JsonSerializer.ApplyValueToEnumerable(Options, ref state, ref value); + JsonSerializer.ApplyValueToEnumerable(Options, ref reader, ref state, ref value); } protected override void OnWrite(ref WriteStackFrame current, Utf8JsonWriter writer) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullableContravariant.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullableContravariant.cs index 6e4f7a4f4a18..43e4621164bf 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullableContravariant.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullableContravariant.cs @@ -56,7 +56,7 @@ protected override void OnReadEnumerable(JsonTokenType tokenType, ref ReadStack } TConverter value = Converter.Read(ref reader, RuntimePropertyType, Options); - JsonSerializer.ApplyValueToEnumerable(Options, ref state, ref value); + JsonSerializer.ApplyValueToEnumerable(Options, ref reader, ref state, ref value); } protected override void OnWrite(ref WriteStackFrame current, Utf8JsonWriter writer) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs index 97369bef9127..64bffa78a007 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs @@ -46,7 +46,7 @@ protected override void OnReadEnumerable(JsonTokenType tokenType, ref ReadStack TProperty value = Converter.Read(ref reader, s_underlyingType, Options); TProperty? nullableValue = new TProperty?(value); - JsonSerializer.ApplyValueToEnumerable(Options, ref state, ref nullableValue); + JsonSerializer.ApplyValueToEnumerable(Options, ref reader, ref state, ref nullableValue); } protected override void OnWrite(ref WriteStackFrame current, Utf8JsonWriter writer) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs index 92fa3b7fd2b2..fae411a8956c 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs @@ -51,6 +51,7 @@ private static void HandleStartArray( private static void HandleEndArray( JsonSerializerOptions options, + ref Utf8JsonReader reader, ref ReadStack state) { Debug.Assert(state.Current.JsonPropertyInfo?.EnumerableConverter != null); @@ -78,13 +79,14 @@ private static void HandleEndArray( Debug.Assert(state.Current.IsProcessingEnumerableOrDictionary); // Outer enumerable or dictionary. - ApplyValueToEnumerable(options, ref state, ref EnumerableInstance); + ApplyValueToEnumerable(options, ref reader, ref state, ref EnumerableInstance); } } } internal static void ApplyValueToEnumerable( JsonSerializerOptions options, + ref Utf8JsonReader reader, ref ReadStack state, ref TProperty value) { @@ -95,7 +97,7 @@ internal static void ApplyValueToEnumerable( if (state.Current.IsProcessingEnumerable) { - jsonPropertyInfo.EnumerableConverter.AddItemToEnumerable(ref state, options, ref value); + jsonPropertyInfo.EnumerableConverter.AddItemToEnumerable(ref reader, ref state, options, ref value); } else if (state.Current.IsProcessingDictionary) { @@ -111,7 +113,7 @@ internal static void ApplyValueToEnumerable( } else { - jsonPropertyInfo.DictionaryConverter.AddItemToDictionary(ref state, options, key, ref value); + jsonPropertyInfo.DictionaryConverter.AddItemToDictionary(ref reader, ref state, options, key, ref value); } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs index b038bbeb43a5..d7a8cd6ec2c1 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs @@ -23,6 +23,7 @@ private static void HandleStartDictionary( private static void HandleEndDictionary( JsonSerializerOptions options, + ref Utf8JsonReader reader, ref ReadStack state) { Debug.Assert(state.Current.JsonPropertyInfo?.DictionaryConverter != null); @@ -40,7 +41,7 @@ private static void HandleEndDictionary( // 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(). - HandleEndObject(options, ref state); + HandleEndObject(options, ref reader, ref state); } else { @@ -64,7 +65,7 @@ private static void HandleEndDictionary( Debug.Assert(state.Current.IsProcessingEnumerableOrDictionary); // Outer enumerable or dictionary. - ApplyValueToEnumerable(options, ref state, ref DictionaryInstance); + ApplyValueToEnumerable(options, ref reader, ref state, ref DictionaryInstance); } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs index 1430f0af88df..ef234edc5acb 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs @@ -90,7 +90,7 @@ private static void AddNullToCollection(JsonPropertyInfo jsonPropertyInfo, ref U { // Assume collection types are reference types and can have null assigned. object value = null; - ApplyValueToEnumerable(options, ref state, ref value); + ApplyValueToEnumerable(options, ref reader, ref state, ref value); } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs index d2ac735d95af..8d97e5e1806a 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs @@ -30,7 +30,7 @@ private static void HandleStartObject(JsonSerializerOptions options, ref ReadSta state.Current.ReturnValue = state.Current.JsonClassInfo.CreateObject(); } - private static void HandleEndObject(JsonSerializerOptions options, ref ReadStack state) + private static void HandleEndObject(JsonSerializerOptions options, ref Utf8JsonReader reader, 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); @@ -54,7 +54,7 @@ private static void HandleEndObject(JsonSerializerOptions options, ref ReadStack if (state.Current.IsProcessingEnumerableOrDictionary) { // Outer enumerable or dictionary. - ApplyValueToEnumerable(options, ref state, ref value); + ApplyValueToEnumerable(options, ref reader, ref state, ref value); } else { diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs index ad9eed972113..6f319dc836c1 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs @@ -101,11 +101,11 @@ private static void ReadCore( } else if (readStack.Current.IsProcessingDictionary) { - HandleEndDictionary(options, ref readStack); + HandleEndDictionary(options, ref reader, ref readStack); } else { - HandleEndObject(options, ref readStack); + HandleEndObject(options, ref reader, ref readStack); } } else if (tokenType == JsonTokenType.StartArray) @@ -133,7 +133,7 @@ private static void ReadCore( } else { - HandleEndArray(options, ref readStack); + HandleEndArray(options, ref reader, ref readStack); } } else if (tokenType == JsonTokenType.Null) From 2de3759fa61ca20852a206f1e955523b8e3f0c7b Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 24 Sep 2019 23:44:01 -0700 Subject: [PATCH 12/15] Trying to fix PolymorphicProperty path. --- .../Json/Serialization/JsonClassInfo.AddProperty.cs | 12 +++++------- .../Text/Json/Serialization/JsonPropertyInfo.cs | 5 +++-- .../Json/Serialization/JsonPropertyInfoCommon.cs | 7 ++++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs index 6e778c981424..447e0b5b59d1 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs @@ -74,7 +74,8 @@ private static JsonPropertyInfo CreateProperty( PropertyInfo propertyInfo, Type parentClassType, JsonConverter converter, - JsonSerializerOptions options) + JsonSerializerOptions options, + Type runtimePropertyType = null) { // Create the JsonPropertyInfo Type propertyInfoClassType; @@ -148,7 +149,7 @@ private static JsonPropertyInfo CreateProperty( args: null, culture: null); - jsonInfo.Initialize(propertyClassType, parentClassType, declaredPropertyType, implementedCollectionType, collectionElementType, propertyInfo, converter, options); + jsonInfo.Initialize(propertyClassType, parentClassType, declaredPropertyType, runtimePropertyType, implementedCollectionType, collectionElementType, propertyInfo, converter, options); return jsonInfo; } @@ -176,11 +177,8 @@ internal JsonPropertyInfo CreatePolymorphicProperty(JsonPropertyInfo property, T property.PropertyInfo, parentClassType: Type, converter: null, - options: options); - - Debugger.Launch(); - - runtimeProperty.RuntimePropertyType = runtimePropertyType; + options: options, + runtimePropertyType: runtimePropertyType); property.CopyRuntimeSettingsTo(runtimeProperty); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs index 002a839b37c9..5dca5afe06cc 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs @@ -166,6 +166,7 @@ public virtual void Initialize( ClassType propertyClassType, Type parentClassType, Type declaredPropertyType, + Type runtimePropertyType, Type implementedCollectionPropertyType, Type collectionElementType, PropertyInfo propertyInfo, @@ -185,10 +186,10 @@ public virtual void Initialize( if (converter != null) { ConverterBase = converter; - RuntimePropertyType = declaredPropertyType; + RuntimePropertyType = runtimePropertyType ?? declaredPropertyType; // Avoid calling GetClassType since it will re-ask if there is a converter which is slow. - if (declaredPropertyType == typeof(object)) + if (RuntimePropertyType == typeof(object)) { ClassType = ClassType.Unknown; } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs index 81c585cfef5a..656592faa160 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs @@ -21,20 +21,21 @@ internal abstract class JsonPropertyInfoCommon Date: Wed, 25 Sep 2019 00:00:42 -0700 Subject: [PATCH 13/15] Working on PolymorphicProperty path. --- .../Json/Serialization/JsonClassInfo.AddProperty.cs | 10 ++++------ .../System/Text/Json/Serialization/JsonPropertyInfo.cs | 5 ++--- .../Text/Json/Serialization/JsonPropertyInfoCommon.cs | 3 +-- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs index 447e0b5b59d1..bd4233d259b5 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs @@ -74,8 +74,7 @@ private static JsonPropertyInfo CreateProperty( PropertyInfo propertyInfo, Type parentClassType, JsonConverter converter, - JsonSerializerOptions options, - Type runtimePropertyType = null) + JsonSerializerOptions options) { // Create the JsonPropertyInfo Type propertyInfoClassType; @@ -149,7 +148,7 @@ private static JsonPropertyInfo CreateProperty( args: null, culture: null); - jsonInfo.Initialize(propertyClassType, parentClassType, declaredPropertyType, runtimePropertyType, implementedCollectionType, collectionElementType, propertyInfo, converter, options); + jsonInfo.Initialize(propertyClassType, parentClassType, declaredPropertyType, implementedCollectionType, collectionElementType, propertyInfo, converter, options); return jsonInfo; } @@ -171,14 +170,13 @@ internal JsonPropertyInfo CreatePolymorphicProperty(JsonPropertyInfo property, T { JsonPropertyInfo runtimeProperty = CreateProperty( property.ClassType, - property.DeclaredPropertyType, + runtimePropertyType, property.ImplementedCollectionPropertyType, property.CollectionElementType, property.PropertyInfo, parentClassType: Type, converter: null, - options: options, - runtimePropertyType: runtimePropertyType); + options: options); property.CopyRuntimeSettingsTo(runtimeProperty); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs index 5dca5afe06cc..002a839b37c9 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs @@ -166,7 +166,6 @@ public virtual void Initialize( ClassType propertyClassType, Type parentClassType, Type declaredPropertyType, - Type runtimePropertyType, Type implementedCollectionPropertyType, Type collectionElementType, PropertyInfo propertyInfo, @@ -186,10 +185,10 @@ public virtual void Initialize( if (converter != null) { ConverterBase = converter; - RuntimePropertyType = runtimePropertyType ?? declaredPropertyType; + RuntimePropertyType = declaredPropertyType; // Avoid calling GetClassType since it will re-ask if there is a converter which is slow. - if (RuntimePropertyType == typeof(object)) + if (declaredPropertyType == typeof(object)) { ClassType = ClassType.Unknown; } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs index 656592faa160..4f4e4a99ab91 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs @@ -22,14 +22,13 @@ public override void Initialize( ClassType propertyClassType, Type parentClassType, Type declaredPropertyType, - Type runtimePropertyType, Type implementedCollectionPropertyType, Type collectionElementType, PropertyInfo propertyInfo, JsonConverter converter, JsonSerializerOptions options) { - base.Initialize(propertyClassType, parentClassType, declaredPropertyType, runtimePropertyType, implementedCollectionPropertyType, collectionElementType, propertyInfo, converter, options); + base.Initialize(propertyClassType, parentClassType, declaredPropertyType, implementedCollectionPropertyType, collectionElementType, propertyInfo, converter, options); if (propertyInfo != null && // We only want to get the getter and setter if we are going to use them. From acb1e5dee006151c4a0667403a48cd590ae39060 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Wed, 25 Sep 2019 22:50:01 -0700 Subject: [PATCH 14/15] PolymorphicProperty tests passing again. --- .../JsonClassInfo.AddProperty.cs | 22 ++++++++++--------- .../Json/Serialization/JsonPropertyInfo.cs | 5 +++-- .../Serialization/JsonPropertyInfoCommon.cs | 3 ++- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs index bd4233d259b5..64fab0d8012b 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs @@ -2,7 +2,6 @@ // 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; @@ -63,12 +62,13 @@ private JsonPropertyInfo AddProperty(ClassType propertyClassType, Type parentCla break; } - return CreateProperty(propertyClassType, propertyType, implementedCollectionType, collectionElementType, propertyInfo, parentClassType, converter, options); + return CreateProperty(propertyClassType, propertyType, propertyType, implementedCollectionType, collectionElementType, propertyInfo, parentClassType, converter, options); } private static JsonPropertyInfo CreateProperty( ClassType propertyClassType, Type declaredPropertyType, + Type runtimePropertyType, Type implementedCollectionType, Type collectionElementType, PropertyInfo propertyInfo, @@ -78,13 +78,13 @@ private static JsonPropertyInfo CreateProperty( { // Create the JsonPropertyInfo Type propertyInfoClassType; - if (declaredPropertyType.IsGenericType && declaredPropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) + if (runtimePropertyType.IsGenericType && runtimePropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { // First try to find a converter for the Nullable, then if not found use the underlying type. // This supports custom converters that want to (de)serialize as null when the value is not null. if (converter == null) { - converter = options.DetermineConverterForProperty(parentClassType, declaredPropertyType, propertyInfo); + converter = options.DetermineConverterForProperty(parentClassType, runtimePropertyType, propertyInfo); } if (converter != null) @@ -92,11 +92,11 @@ private static JsonPropertyInfo CreateProperty( propertyInfoClassType = typeof(JsonPropertyInfoNotNullable<,,>).MakeGenericType( parentClassType, declaredPropertyType, - declaredPropertyType); + runtimePropertyType); } else { - Type typeToConvert = Nullable.GetUnderlyingType(declaredPropertyType); + Type typeToConvert = Nullable.GetUnderlyingType(runtimePropertyType); converter = options.DetermineConverterForProperty(parentClassType, typeToConvert, propertyInfo); propertyInfoClassType = typeof(JsonPropertyInfoNullable<,>).MakeGenericType(parentClassType, typeToConvert); } @@ -105,7 +105,7 @@ private static JsonPropertyInfo CreateProperty( { if (converter == null) { - converter = options.DetermineConverterForProperty(parentClassType, declaredPropertyType, propertyInfo); + converter = options.DetermineConverterForProperty(parentClassType, runtimePropertyType, propertyInfo); } Type typeToConvert = converter?.TypeToConvert; @@ -122,7 +122,7 @@ private static JsonPropertyInfo CreateProperty( } // For the covariant case, create JsonPropertyInfoNotNullable. The generic constraints are "where TConverter : TDeclaredProperty". - if (declaredPropertyType.IsAssignableFrom(typeToConvert)) + if (runtimePropertyType.IsAssignableFrom(typeToConvert)) { propertyInfoClassType = typeof(JsonPropertyInfoNotNullable<,,>).MakeGenericType( parentClassType, @@ -131,7 +131,7 @@ private static JsonPropertyInfo CreateProperty( } else { - Debug.Assert(typeToConvert.IsAssignableFrom(declaredPropertyType)); + Debug.Assert(typeToConvert.IsAssignableFrom(runtimePropertyType)); // For the contravariant case, create JsonPropertyInfoNotNullableContravariant. The generic constraints are "where TDeclaredProperty : TConverter". propertyInfoClassType = typeof(JsonPropertyInfoNotNullableContravariant<,,>).MakeGenericType( @@ -148,7 +148,7 @@ private static JsonPropertyInfo CreateProperty( args: null, culture: null); - jsonInfo.Initialize(propertyClassType, parentClassType, declaredPropertyType, implementedCollectionType, collectionElementType, propertyInfo, converter, options); + jsonInfo.Initialize(propertyClassType, parentClassType, declaredPropertyType, runtimePropertyType, implementedCollectionType, collectionElementType, propertyInfo, converter, options); return jsonInfo; } @@ -158,6 +158,7 @@ internal JsonPropertyInfo CreateRootObject(JsonSerializerOptions options) return CreateProperty( ClassType.Object, declaredPropertyType: Type, + runtimePropertyType: Type, implementedCollectionType: Type, collectionElementType: null, propertyInfo: null, @@ -170,6 +171,7 @@ internal JsonPropertyInfo CreatePolymorphicProperty(JsonPropertyInfo property, T { JsonPropertyInfo runtimeProperty = CreateProperty( property.ClassType, + property.DeclaredPropertyType, runtimePropertyType, property.ImplementedCollectionPropertyType, property.CollectionElementType, diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs index 002a839b37c9..903a9100b4e3 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs @@ -166,6 +166,7 @@ public virtual void Initialize( ClassType propertyClassType, Type parentClassType, Type declaredPropertyType, + Type runtimePropertyType, Type implementedCollectionPropertyType, Type collectionElementType, PropertyInfo propertyInfo, @@ -175,6 +176,7 @@ public virtual void Initialize( ClassType = propertyClassType; ParentClassType = parentClassType; DeclaredPropertyType = declaredPropertyType; + RuntimePropertyType = runtimePropertyType; ImplementedCollectionPropertyType = implementedCollectionPropertyType; CollectionElementType = collectionElementType; PropertyInfo = propertyInfo; @@ -185,10 +187,9 @@ public virtual void Initialize( if (converter != null) { ConverterBase = converter; - RuntimePropertyType = declaredPropertyType; // Avoid calling GetClassType since it will re-ask if there is a converter which is slow. - if (declaredPropertyType == typeof(object)) + if (runtimePropertyType == typeof(object)) { ClassType = ClassType.Unknown; } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs index 4f4e4a99ab91..656592faa160 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs @@ -22,13 +22,14 @@ public override void Initialize( ClassType propertyClassType, Type parentClassType, Type declaredPropertyType, + Type runtimePropertyType, Type implementedCollectionPropertyType, Type collectionElementType, PropertyInfo propertyInfo, JsonConverter converter, JsonSerializerOptions options) { - base.Initialize(propertyClassType, parentClassType, declaredPropertyType, implementedCollectionPropertyType, collectionElementType, propertyInfo, converter, options); + base.Initialize(propertyClassType, parentClassType, declaredPropertyType, runtimePropertyType, implementedCollectionPropertyType, collectionElementType, propertyInfo, converter, options); if (propertyInfo != null && // We only want to get the getter and setter if we are going to use them. From fb557fc849b559fab1e163af73c59217f003bdff Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 26 Sep 2019 13:55:26 -0700 Subject: [PATCH 15/15] Tests are finally passing. --- .../Converters/DefaultArrayConverter.cs | 6 +- .../DefaultDerivedDictionaryConverter.cs | 4 +- .../DefaultDerivedEnumerableConverter.cs | 4 +- .../Converters/DefaultICollectionConverter.cs | 9 +- .../Converters/DefaultIDictionaryConverter.cs | 9 +- .../DefaultImmutableDictionaryConverter.cs | 24 +-- .../DefaultImmutableEnumerableConverter.cs | 19 ++- .../JsonClassInfo.AddProperty.cs | 27 ++- .../Serialization/JsonClassInfo.Helpers.cs | 154 ++---------------- .../Serialization/JsonDictionaryConverter.cs | 4 +- .../Serialization/JsonEnumerableConverter.cs | 2 +- .../Json/Serialization/JsonPropertyInfo.cs | 12 +- .../JsonSerializer.Read.HandleNull.cs | 3 + .../Text/Json/Serialization/MemberAccessor.cs | 2 +- .../tests/Serialization/DictionaryTests.cs | 19 +-- .../Value.ReadTests.GenericCollections.cs | 32 +++- .../Value.ReadTests.ImmutableCollections.cs | 132 +++++++++++---- 17 files changed, 209 insertions(+), 253 deletions(-) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultArrayConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultArrayConverter.cs index 88de5ec3cbd1..a6c6461c5a9d 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultArrayConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultArrayConverter.cs @@ -9,16 +9,16 @@ namespace System.Text.Json.Serialization.Converters { internal sealed class DefaultArrayConverter : JsonTemporaryListConverter { - public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) + public override bool OwnsImplementedCollectionType(Type declaredPropertyType, Type implementedCollectionType, Type collectionElementType) { return implementedCollectionType.IsArray; } public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) { - Debug.Assert(jsonPropertyInfo.DeclaredPropertyType.IsArray); + Debug.Assert(jsonPropertyInfo.RuntimePropertyType.IsArray); - return jsonPropertyInfo.DeclaredPropertyType; + return jsonPropertyInfo.RuntimePropertyType; } public override object EndEnumerable(ref ReadStack state, JsonSerializerOptions options) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs index cb539ed05f0b..6d4a5adff47f 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.cs @@ -15,7 +15,7 @@ internal sealed class DefaultDerivedDictionaryConverter : JsonDictionaryConverte private static readonly ConcurrentDictionary s_ctors = new ConcurrentDictionary(); private static readonly ConcurrentDictionary s_dictionaryBuilderCtors = new ConcurrentDictionary(); - public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) + public override bool OwnsImplementedCollectionType(Type declaredPropertyType, Type implementedCollectionType, Type collectionElementType) { return typeof(IDictionary).IsAssignableFrom(implementedCollectionType) || (implementedCollectionType.IsGenericType && typeof(IDictionary<,>).MakeGenericType(typeof(string), collectionElementType).IsAssignableFrom(implementedCollectionType)); @@ -28,7 +28,7 @@ public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) return typeof(Dictionary<,>).MakeGenericType(typeof(string), jsonPropertyInfo.CollectionElementType); } - return jsonPropertyInfo.DeclaredPropertyType; + return jsonPropertyInfo.RuntimePropertyType; } public override void BeginDictionary(ref ReadStack state, JsonSerializerOptions options) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs index fd4b6fc656d5..e3bf8206ec4a 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.cs @@ -16,7 +16,7 @@ internal sealed class DefaultDerivedEnumerableConverter : JsonEnumerableConverte private static readonly ConcurrentDictionary s_ctors = new ConcurrentDictionary(); private static readonly ConcurrentDictionary s_collectonBuilderCtors = new ConcurrentDictionary(); - public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) + public override bool OwnsImplementedCollectionType(Type declaredPropertyType, Type implementedCollectionType, Type collectionElementType) { return typeof(IEnumerable).IsAssignableFrom(implementedCollectionType); } @@ -40,7 +40,7 @@ public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) return typeof(List<>).MakeGenericType(jsonPropertyInfo.CollectionElementType); } - return jsonPropertyInfo.DeclaredPropertyType; + return jsonPropertyInfo.RuntimePropertyType; } public override void BeginEnumerable(ref ReadStack state, JsonSerializerOptions options) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs index 0266834f8124..aed979c9d868 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs @@ -14,7 +14,7 @@ internal sealed class DefaultICollectionConverter : JsonTemporaryListConverter // Cache factories for performance. private static readonly ConcurrentDictionary s_factories = new ConcurrentDictionary(); - public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) + public override bool OwnsImplementedCollectionType(Type declaredPropertyType, Type implementedCollectionType, Type collectionElementType) { return JsonClassInfo.IsDeserializedByConstructingWithIList(implementedCollectionType); } @@ -38,7 +38,7 @@ public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) ThrowHelper.ThrowInvalidOperationException_DeserializePolymorphicInterface(implementedCollectionPropertyType); } - return jsonPropertyInfo.DeclaredPropertyType; + return jsonPropertyInfo.RuntimePropertyType; } protected override Type ResolveTemporaryListType(JsonPropertyInfo jsonPropertyInfo) @@ -48,7 +48,7 @@ protected override Type ResolveTemporaryListType(JsonPropertyInfo jsonPropertyIn if (implementedCollectionPropertyType.IsGenericType && implementedCollectionPropertyType.GetGenericTypeDefinition().FullName == JsonClassInfo.ReadOnlyObservableCollectionGenericTypeName) { - return implementedCollectionPropertyType.Assembly.GetType(JsonClassInfo.ObservableCollectionGenericTypeName); + return Type.GetType($"{JsonClassInfo.ObservableCollectionGenericTypeName}, System.ObjectModel"); } return base.ResolveTemporaryListType(jsonPropertyInfo); @@ -62,9 +62,8 @@ public override object EndEnumerable(ref ReadStack state, JsonSerializerOptions JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; Type collectionType = jsonPropertyInfo.RuntimePropertyType; - Type implementedCollectionType = jsonPropertyInfo.ImplementedCollectionPropertyType; - if (implementedCollectionType == typeof(ArrayList)) + if (collectionType == typeof(ArrayList)) { return new ArrayList(sourceList); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs index e7479143109c..9d0871a5b628 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs @@ -13,7 +13,7 @@ internal sealed class DefaultIDictionaryConverter : JsonTemporaryDictionaryConve // Cache factories for performance. private static readonly ConcurrentDictionary s_factories = new ConcurrentDictionary(); - public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) + public override bool OwnsImplementedCollectionType(Type declaredPropertyType, Type implementedCollectionType, Type collectionElementType) { return JsonClassInfo.IsDeserializedByConstructingWithIDictionary(implementedCollectionType); } @@ -29,14 +29,14 @@ public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) switch (implementedCollectionPropertyType.GetGenericTypeDefinition().FullName) { case JsonClassInfo.ReadOnlyDictionaryGenericInterfaceTypeName: - return implementedCollectionPropertyType.Assembly.GetType(JsonClassInfo.ReadOnlyDictionaryGenericTypeName).MakeGenericType(typeof(string), jsonPropertyInfo.CollectionElementType); + return Type.GetType($"{JsonClassInfo.ReadOnlyDictionaryGenericTypeName}, System.ObjectModel").MakeGenericType(typeof(string), jsonPropertyInfo.CollectionElementType); } } ThrowHelper.ThrowInvalidOperationException_DeserializePolymorphicInterface(implementedCollectionPropertyType); } - return jsonPropertyInfo.DeclaredPropertyType; + return jsonPropertyInfo.RuntimePropertyType; } public override object EndDictionary(ref ReadStack state, JsonSerializerOptions options) @@ -47,9 +47,8 @@ public override object EndDictionary(ref ReadStack state, JsonSerializerOptions JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; Type dictionaryType = jsonPropertyInfo.RuntimePropertyType; - Type implementedCollectionType = jsonPropertyInfo.ImplementedCollectionPropertyType; - if (implementedCollectionType == typeof(Hashtable)) + if (dictionaryType == typeof(Hashtable)) { return new Hashtable(sourceDictionary); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs index 30d43341c9fb..7372be185955 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs @@ -38,32 +38,14 @@ public static void RegisterImmutableDictionary(Type immutableCollectionType, Typ options.TryAddCreateRangeDelegate(delegateKey, createRangeDelegate); } - public static bool IsImmutableDictionary(Type type) + public override bool OwnsImplementedCollectionType(Type declaredPropertyType, Type implementedCollectionType, Type collectionElementType) { - if (!type.IsGenericType) - { - return false; - } - - switch (type.GetGenericTypeDefinition().FullName) - { - case ImmutableDictionaryGenericTypeName: - case ImmutableDictionaryGenericInterfaceTypeName: - case ImmutableSortedDictionaryGenericTypeName: - return true; - default: - return false; - } - } - - public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) - { - return implementedCollectionType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName); + return declaredPropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName); } public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) { - return jsonPropertyInfo.DeclaredPropertyType; + return jsonPropertyInfo.RuntimePropertyType; } public override object EndDictionary(ref ReadStack state, JsonSerializerOptions options) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs index 0e91ff2932e3..c754e773559d 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs @@ -104,31 +104,32 @@ public static void RegisterImmutableCollection(Type immutableCollectionType, Typ options.TryAddCreateRangeDelegate(delegateKey, createRangeDelegate); } - public override bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType) + public override bool OwnsImplementedCollectionType(Type declaredPropertyType, Type implementedCollectionType, Type collectionElementType) { - return implementedCollectionType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName); + return declaredPropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName); } public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) { Type implementedCollectionPropertyType = jsonPropertyInfo.ImplementedCollectionPropertyType; Type collectionElementType = jsonPropertyInfo.CollectionElementType; + if (implementedCollectionPropertyType.IsInterface) { Type runtimeType = null; switch (implementedCollectionPropertyType.GetGenericTypeDefinition().FullName) { case ImmutableListGenericInterfaceTypeName: - runtimeType = ResolveConcreteImmutableType(implementedCollectionPropertyType, collectionElementType, ImmutableListGenericTypeName); + runtimeType = ResolveConcreteImmutableType(collectionElementType, ImmutableListGenericTypeName); break; case ImmutableQueueGenericInterfaceTypeName: - runtimeType = ResolveConcreteImmutableType(implementedCollectionPropertyType, collectionElementType, ImmutableQueueGenericTypeName); + runtimeType = ResolveConcreteImmutableType(collectionElementType, ImmutableQueueGenericTypeName); break; case ImmutableSetGenericInterfaceTypeName: - runtimeType = ResolveConcreteImmutableType(implementedCollectionPropertyType, collectionElementType, ImmutableHashSetGenericTypeName); + runtimeType = ResolveConcreteImmutableType(collectionElementType, ImmutableHashSetGenericTypeName); break; case ImmutableStackGenericInterfaceTypeName: - runtimeType = ResolveConcreteImmutableType(implementedCollectionPropertyType, collectionElementType, ImmutableStackGenericInterfaceTypeName); + runtimeType = ResolveConcreteImmutableType(collectionElementType, ImmutableStackGenericInterfaceTypeName); break; } if (runtimeType == null) @@ -138,12 +139,12 @@ public override Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo) return runtimeType; } - return jsonPropertyInfo.DeclaredPropertyType; + return jsonPropertyInfo.RuntimePropertyType; } - private static Type ResolveConcreteImmutableType(Type implementedCollectionPropertyType, Type collectionElementType, string typeName) + private static Type ResolveConcreteImmutableType(Type collectionElementType, string typeName) { - return implementedCollectionPropertyType.Assembly.GetType(typeName)?.MakeGenericType(collectionElementType); + return Type.GetType($"{typeName}, System.Collections.Immutable")?.MakeGenericType(collectionElementType); } public override object EndEnumerable(ref ReadStack state, JsonSerializerOptions options) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs index 64fab0d8012b..cd0229e17c5e 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs @@ -52,17 +52,22 @@ private JsonPropertyInfo AddProperty(ClassType propertyClassType, Type parentCla return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(propertyInfo, options); } - Type collectionElementType = null; + Type collectionElementType = GetCollectionElementType(propertyClassType, parentClassType, propertyType, propertyInfo, implementedCollectionType); + + return CreateProperty(propertyClassType, propertyType, propertyType, implementedCollectionType, collectionElementType, propertyInfo, parentClassType, converter, options); + } + + private Type GetCollectionElementType(ClassType propertyClassType, Type parentClassType, Type propertyType, PropertyInfo propertyInfo, Type implementedCollectionType) + { switch (propertyClassType) { case ClassType.Enumerable: case ClassType.Dictionary: case ClassType.Unknown: - collectionElementType = GetElementType(propertyClassType, propertyType, implementedCollectionType, parentClassType, propertyInfo); - break; + return GetElementType(propertyClassType, propertyType, implementedCollectionType, parentClassType, propertyInfo); } - return CreateProperty(propertyClassType, propertyType, propertyType, implementedCollectionType, collectionElementType, propertyInfo, parentClassType, converter, options); + return null; } private static JsonPropertyInfo CreateProperty( @@ -169,15 +174,21 @@ internal JsonPropertyInfo CreateRootObject(JsonSerializerOptions options) internal JsonPropertyInfo CreatePolymorphicProperty(JsonPropertyInfo property, Type runtimePropertyType, JsonSerializerOptions options) { + Type implementedCollectionType = GetImplementedCollectionType(Type, runtimePropertyType, property.PropertyInfo, out JsonConverter converter, options); + + ClassType classType = GetClassType(runtimePropertyType, implementedCollectionType, options); + + Type collectionElementType = GetCollectionElementType(classType, Type, runtimePropertyType, property.PropertyInfo, implementedCollectionType); + JsonPropertyInfo runtimeProperty = CreateProperty( - property.ClassType, + classType, property.DeclaredPropertyType, runtimePropertyType, - property.ImplementedCollectionPropertyType, - property.CollectionElementType, + implementedCollectionType, + collectionElementType, property.PropertyInfo, parentClassType: Type, - converter: null, + converter: converter, options: options); property.CopyRuntimeSettingsTo(runtimeProperty); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs index 028d8d3bfcf0..de2bb00879c5 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Helpers.cs @@ -58,29 +58,6 @@ internal partial class JsonClassInfo 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 s_nativelySupportedGenericCollections = new HashSet() { @@ -174,32 +151,19 @@ public static Type GetImplementedCollectionType( baseType = baseType.BaseType; } - // Try generic interfaces with add methods. - foreach (Type candidate in s_genericInterfacesWithAddMethods) + foreach (Type implementedInterface in queryType.GetInterfaces()) { - Type derivedGeneric = ExtractGenericInterface(queryType, candidate); - if (derivedGeneric != null) + if (implementedInterface.IsGenericType) { - return derivedGeneric; + Type genericTypeDefinition = implementedInterface.GetGenericTypeDefinition(); + if (IsNativelySupportedCollection(genericTypeDefinition)) + { + return implementedInterface; + } } - } - - // Try non-generic interfaces with add methods. - foreach (Type candidate in s_nonGenericInterfacesWithAddMethods) - { - if (candidate.IsAssignableFrom(queryType)) + else if (IsNativelySupportedCollection(implementedInterface)) { - 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 implementedInterface; } } @@ -210,6 +174,9 @@ public static bool IsDeserializedByConstructingWithIList(Type type) { if (type.IsGenericType) { + if (type.IsInterface && type.FullName.StartsWith(ImmutableNamespaceName)) + return true; + switch (type.GetGenericTypeDefinition().FullName) { // interfaces @@ -241,6 +208,9 @@ public static bool IsDeserializedByConstructingWithIDictionary(Type type) { if (type.IsGenericType) { + if (type.IsInterface && type.FullName.StartsWith(ImmutableNamespaceName)) + return true; + switch (type.GetGenericTypeDefinition().FullName) { case ReadOnlyDictionaryGenericInterfaceTypeName: @@ -265,99 +235,5 @@ public static bool IsNativelySupportedCollection(Type queryType) 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. - - /// - /// Determine whether is or implements a closed generic - /// created from . - /// - /// The of interest. - /// The open generic to match. Usually an interface. - /// - /// The closed generic created from that - /// is or implements. null if the two s have no such - /// relationship. - /// - /// - /// This method will return if is - /// typeof(KeyValuePair{,}), and is - /// typeof(KeyValuePair{string, object}). - /// - 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); - } - } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs index 214a1faff48f..839341769e58 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs @@ -63,7 +63,7 @@ public abstract class WrappedDictionaryFactory } public sealed class WrappedDictionaryFactory : WrappedDictionaryFactory - where TDictionary : IDictionary + where TDictionary : IEnumerable where TSourceDictionary : IDictionary { private readonly DictionaryConstructorDelegate _ctor; @@ -161,7 +161,7 @@ private IDictionary CreateConcreteDictionary(JsonPropertyInfo jsonPropertyInfo, // implement KeyValuePair<,>. internal abstract class JsonDictionaryConverter { - public abstract bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType); + public abstract bool OwnsImplementedCollectionType(Type declaredPropertyType, Type implementedCollectionType, Type collectionElementType); public abstract Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo); public abstract void BeginDictionary(ref ReadStack state, JsonSerializerOptions options); public abstract void AddItemToDictionary(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options, string key, ref T value); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs index f55a9df1bc74..36ea1683b614 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs @@ -159,7 +159,7 @@ private IList CreateConcreteList(JsonPropertyInfo jsonPropertyInfo, JsonSerializ internal abstract class JsonEnumerableConverter { - public abstract bool OwnsImplementedCollectionType(Type implementedCollectionType, Type collectionElementType); + public abstract bool OwnsImplementedCollectionType(Type declaredPropertyType, Type implementedCollectionType, Type collectionElementType); public abstract Type ResolveRunTimeType(JsonPropertyInfo jsonPropertyInfo); public abstract void BeginEnumerable(ref ReadStack state, JsonSerializerOptions options); public abstract void AddItemToEnumerable(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options, ref T value); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs index 903a9100b4e3..7d979e443e50 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs @@ -437,7 +437,7 @@ private void DetermineEnumerableOrDictionaryConverter() } else if (ClassType == ClassType.Dictionary) { - if (s_jsonImmutableDictionaryConverter.OwnsImplementedCollectionType(ImplementedCollectionPropertyType, CollectionElementType)) + if (s_jsonImmutableDictionaryConverter.OwnsImplementedCollectionType(DeclaredPropertyType, ImplementedCollectionPropertyType, CollectionElementType)) { DictionaryConverter = s_jsonImmutableDictionaryConverter; @@ -445,13 +445,13 @@ private void DetermineEnumerableOrDictionaryConverter() DefaultImmutableDictionaryConverter.RegisterImmutableDictionary(RuntimePropertyType, CollectionElementType, Options); } - else if (s_jsonIDictionaryConverter.OwnsImplementedCollectionType(ImplementedCollectionPropertyType, CollectionElementType)) + else if (s_jsonIDictionaryConverter.OwnsImplementedCollectionType(DeclaredPropertyType, ImplementedCollectionPropertyType, CollectionElementType)) { DictionaryConverter = s_jsonIDictionaryConverter; RuntimePropertyType = DictionaryConverter.ResolveRunTimeType(this); } - else if (s_jsonDerivedDictionaryConverter.OwnsImplementedCollectionType(ImplementedCollectionPropertyType, CollectionElementType)) + else if (s_jsonDerivedDictionaryConverter.OwnsImplementedCollectionType(DeclaredPropertyType, ImplementedCollectionPropertyType, CollectionElementType)) { DictionaryConverter = s_jsonDerivedDictionaryConverter; @@ -464,7 +464,7 @@ private void DetermineEnumerableOrDictionaryConverter() } else if (ClassType == ClassType.Enumerable) { - if (s_jsonImmutableEnumerableConverter.OwnsImplementedCollectionType(ImplementedCollectionPropertyType, CollectionElementType)) + if (s_jsonImmutableEnumerableConverter.OwnsImplementedCollectionType(DeclaredPropertyType, ImplementedCollectionPropertyType, CollectionElementType)) { EnumerableConverter = s_jsonImmutableEnumerableConverter; @@ -472,13 +472,13 @@ private void DetermineEnumerableOrDictionaryConverter() DefaultImmutableEnumerableConverter.RegisterImmutableCollection(RuntimePropertyType, CollectionElementType, Options); } - else if (s_jsonICollectionConverter.OwnsImplementedCollectionType(ImplementedCollectionPropertyType, CollectionElementType)) + else if (s_jsonICollectionConverter.OwnsImplementedCollectionType(DeclaredPropertyType, ImplementedCollectionPropertyType, CollectionElementType)) { EnumerableConverter = s_jsonICollectionConverter; RuntimePropertyType = EnumerableConverter.ResolveRunTimeType(this); } - else if (s_jsonDerivedEnumerableConverter.OwnsImplementedCollectionType(ImplementedCollectionPropertyType, CollectionElementType)) + else if (s_jsonDerivedEnumerableConverter.OwnsImplementedCollectionType(DeclaredPropertyType, ImplementedCollectionPropertyType, CollectionElementType)) { EnumerableConverter = s_jsonDerivedEnumerableConverter; diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs index ef234edc5acb..fe77ae46856b 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs @@ -45,6 +45,9 @@ private static bool HandleNull(JsonSerializerOptions options, ref Utf8JsonReader } else { + // Set the property to null. + state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value: null); + // Reset so that `Is*Property` no longer returns true state.Current.EndProperty(); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs index 54d12c574161..72d2f11e074e 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs @@ -26,7 +26,7 @@ public abstract JsonEnumerableConverterState.EnumerableConstructorDelegate CreateDictionaryConstructor() - where TDictionary : IDictionary + where TDictionary : IEnumerable where TSourceDictionary : IDictionary; public abstract ImmutableCollectionCreator ImmutableCollectionCreateRange(Type constructingType, Type collectionType, Type elementType); diff --git a/src/System.Text.Json/tests/Serialization/DictionaryTests.cs b/src/System.Text.Json/tests/Serialization/DictionaryTests.cs index 0ed73efddf42..7779eb9c995f 100644 --- a/src/System.Text.Json/tests/Serialization/DictionaryTests.cs +++ b/src/System.Text.Json/tests/Serialization/DictionaryTests.cs @@ -195,13 +195,10 @@ public static void ImplementsDictionary_DictionaryOfString() } { - Assert.Throws(() => JsonSerializer.Deserialize(JsonString)); + StringToStringIReadOnlyDictionaryWrapper obj = JsonSerializer.Deserialize(JsonString); + Assert.Equal("World", obj["Hello"]); + Assert.Equal("World2", obj["Hello2"]); - StringToStringIReadOnlyDictionaryWrapper obj = new StringToStringIReadOnlyDictionaryWrapper(new Dictionary() - { - { "Hello", "World" }, - { "Hello2", "World2" }, - }); string json = JsonSerializer.Serialize(obj); Assert.Equal(JsonString, json); @@ -210,13 +207,9 @@ public static void ImplementsDictionary_DictionaryOfString() } { - Assert.Throws(() => JsonSerializer.Deserialize(JsonString)); - - StringToStringIImmutableDictionaryWrapper obj = new StringToStringIImmutableDictionaryWrapper(new Dictionary() - { - { "Hello", "World" }, - { "Hello2", "World2" }, - }); + StringToStringIImmutableDictionaryWrapper obj = JsonSerializer.Deserialize(JsonString); + Assert.Equal("World", obj["Hello"]); + Assert.Equal("World2", obj["Hello2"]); string json = JsonSerializer.Serialize(obj); Assert.True(JsonString == json || ReorderedJsonString == json); diff --git a/src/System.Text.Json/tests/Serialization/Value.ReadTests.GenericCollections.cs b/src/System.Text.Json/tests/Serialization/Value.ReadTests.GenericCollections.cs index ab1def885741..d6cfc7bfedcf 100644 --- a/src/System.Text.Json/tests/Serialization/Value.ReadTests.GenericCollections.cs +++ b/src/System.Text.Json/tests/Serialization/Value.ReadTests.GenericCollections.cs @@ -1138,7 +1138,6 @@ public static void ReadSimpleTestClass_GenericWrappers_NoAddMethod_Throws() Assert.Throws(() => JsonSerializer.Deserialize(SimpleTestClassWithStringIEnumerableWrapper.s_json)); Assert.Throws(() => JsonSerializer.Deserialize(SimpleTestClassWithStringIReadOnlyCollectionWrapper.s_json)); Assert.Throws(() => JsonSerializer.Deserialize(SimpleTestClassWithStringIReadOnlyListWrapper.s_json)); - Assert.Throws(() => JsonSerializer.Deserialize(SimpleTestClassWithStringToStringIReadOnlyDictionaryWrapper.s_json)); } [Fact] @@ -1148,11 +1147,34 @@ public static void ReadPrimitiveStringCollection_Throws() } [Fact] - public static void ReadReadOnlyCollections_Throws() + public static void ReadReadOnlyCollections_Supported() { - Assert.Throws(() => JsonSerializer.Deserialize(@"[""1"", ""2""]")); - Assert.Throws(() => JsonSerializer.Deserialize(@"[""1"", ""2""]")); - Assert.Throws(() => JsonSerializer.Deserialize(@"{""Key"":""key"",""Value"":""value""}")); + { + ReadOnlyStringIListWrapper obj = JsonSerializer.Deserialize(@"[""1"", ""2""]"); + + Assert.NotNull(obj); + Assert.Equal(2, obj.Count); + Assert.Equal("1", obj[0]); + Assert.Equal("2", obj[1]); + } + + { + ReadOnlyStringICollectionWrapper obj = JsonSerializer.Deserialize(@"[""1"", ""2""]"); + + Assert.NotNull(obj); + Assert.Equal(2, obj.Count); + Assert.Equal("1", obj.First()); + Assert.Equal("2", obj.Skip(1).First()); + } + + { + ReadOnlyStringToStringIDictionaryWrapper obj = JsonSerializer.Deserialize(@"{""Key"":""key"",""Value"":""value""}"); + + Assert.NotNull(obj); + Assert.Equal(2, obj.Count); + Assert.Equal("key", obj["Key"]); + Assert.Equal("value", obj["Value"]); + } } } } diff --git a/src/System.Text.Json/tests/Serialization/Value.ReadTests.ImmutableCollections.cs b/src/System.Text.Json/tests/Serialization/Value.ReadTests.ImmutableCollections.cs index e6b8098cd614..7ecef36c9d3a 100644 --- a/src/System.Text.Json/tests/Serialization/Value.ReadTests.ImmutableCollections.cs +++ b/src/System.Text.Json/tests/Serialization/Value.ReadTests.ImmutableCollections.cs @@ -127,19 +127,33 @@ public static void ReadArrayOfIIImmutableListT() [Fact] public static void ReadPrimitiveIImmutableListT() { - IImmutableList result = JsonSerializer.Deserialize>(Encoding.UTF8.GetBytes(@"[1,2]")); - int expected = 1; + { + IImmutableList result = JsonSerializer.Deserialize>(Encoding.UTF8.GetBytes(@"[1,2]")); + int expected = 1; + + foreach (int i in result) + { + Assert.Equal(expected++, i); + } + } - foreach (int i in result) { - Assert.Equal(expected++, i); + IImmutableList result = JsonSerializer.Deserialize>(Encoding.UTF8.GetBytes(@"[]")); + Assert.Equal(0, result.Count()); } - result = JsonSerializer.Deserialize>(Encoding.UTF8.GetBytes(@"[]")); - Assert.Equal(0, result.Count()); + { + StringIImmutableListWrapper result = JsonSerializer.Deserialize(@"[""1"",""2""]"); + + Assert.Equal(2, result.Count()); + Assert.Equal("1", result[0]); + Assert.Equal("2", result[1]); + } - Assert.Throws(() => JsonSerializer.Deserialize(@"[""1"",""2""]")); - Assert.Throws(() => JsonSerializer.Deserialize(@"[]")); + { + StringIImmutableListWrapper result = JsonSerializer.Deserialize(@"[]"); + Assert.Equal(0, result.Count()); + } } [Fact] @@ -194,19 +208,30 @@ public static void ReadArrayOfIIImmutableStackT() [Fact] public static void ReadPrimitiveIImmutableStackT() { - IImmutableStack result = JsonSerializer.Deserialize>(Encoding.UTF8.GetBytes(@"[1,2]")); - int expected = 2; + { + IImmutableStack result = JsonSerializer.Deserialize>(Encoding.UTF8.GetBytes(@"[1,2]")); + int expected = 2; + + foreach (int i in result) + { + Assert.Equal(expected--, i); + } + } - foreach (int i in result) { - Assert.Equal(expected--, i); + IImmutableStack result = JsonSerializer.Deserialize>(Encoding.UTF8.GetBytes(@"[]")); + Assert.Equal(0, result.Count()); } - result = JsonSerializer.Deserialize>(Encoding.UTF8.GetBytes(@"[]")); - Assert.Equal(0, result.Count()); + { + StringIImmutableStackWrapper result = JsonSerializer.Deserialize(@"[""1"",""2""]"); + Assert.Equal(2, result.Count()); + } - Assert.Throws(() => JsonSerializer.Deserialize(@"[""1"",""2""]")); - Assert.Throws(() => JsonSerializer.Deserialize(@"[]")); + { + StringIImmutableStackWrapper result = JsonSerializer.Deserialize(@"[]"); + Assert.Equal(0, result.Count()); + } } [Fact] @@ -257,19 +282,30 @@ public static void ReadArrayOfIImmutableQueueT() [Fact] public static void ReadPrimitiveIImmutableQueueT() { - IImmutableQueue result = JsonSerializer.Deserialize>(Encoding.UTF8.GetBytes(@"[1,2]")); - int expected = 1; - - foreach (int i in result) { - Assert.Equal(expected++, i); + IImmutableQueue result = JsonSerializer.Deserialize>(Encoding.UTF8.GetBytes(@"[1,2]")); + int expected = 1; + + foreach (int i in result) + { + Assert.Equal(expected++, i); + } } - result = JsonSerializer.Deserialize>(Encoding.UTF8.GetBytes(@"[]")); - Assert.Equal(0, result.Count()); + { + IImmutableQueue result = JsonSerializer.Deserialize>(Encoding.UTF8.GetBytes(@"[]")); + Assert.Equal(0, result.Count()); + } + + { + StringIImmutableQueueWrapper result = JsonSerializer.Deserialize(@"[""1"",""2""]"); + Assert.Equal(2, result.Count()); + } - Assert.Throws(() => JsonSerializer.Deserialize(@"[""1"",""2""]")); - Assert.Throws(() => JsonSerializer.Deserialize(@"[]")); + { + StringIImmutableQueueWrapper result = JsonSerializer.Deserialize(@"[]"); + Assert.Equal(0, result.Count()); + } } [Fact] @@ -630,13 +666,47 @@ public static void ReadPrimitiveImmutableSortedSetT() } [Fact] - public static void ReadSimpleTestClass_ImmutableCollectionWrappers_Throws() + public static void ReadSimpleTestClass_ImmutableCollectionWrappers_Supported() { - Assert.Throws(() => JsonSerializer.Deserialize(SimpleTestClassWithIImmutableDictionaryWrapper.s_json)); - Assert.Throws(() => JsonSerializer.Deserialize(SimpleTestClassWithImmutableListWrapper.s_json)); - Assert.Throws(() => JsonSerializer.Deserialize(SimpleTestClassWithImmutableStackWrapper.s_json)); - Assert.Throws(() => JsonSerializer.Deserialize(SimpleTestClassWithImmutableQueueWrapper.s_json)); - Assert.Throws(() => JsonSerializer.Deserialize(SimpleTestClassWithImmutableSetWrapper.s_json)); + { + SimpleTestClassWithImmutableListWrapper result = JsonSerializer.Deserialize(SimpleTestClassWithImmutableListWrapper.s_json); + + Assert.NotNull(result?.MyStringIImmutableListWrapper); + Assert.Equal(1, result.MyStringIImmutableListWrapper.Count); + Assert.Equal("Hello", result.MyStringIImmutableListWrapper[0]); + } + + { + SimpleTestClassWithImmutableSetWrapper result = JsonSerializer.Deserialize(SimpleTestClassWithImmutableSetWrapper.s_json); + + Assert.NotNull(result?.MyStringIImmutableSetWrapper); + Assert.Equal(1, result.MyStringIImmutableSetWrapper.Count); + Assert.Equal("Hello", result.MyStringIImmutableSetWrapper.First()); + } + + { + SimpleTestClassWithIImmutableDictionaryWrapper result = JsonSerializer.Deserialize(SimpleTestClassWithIImmutableDictionaryWrapper.s_json); + + Assert.NotNull(result?.MyStringToStringImmutableDictionaryWrapper); + Assert.Equal(1, result.MyStringToStringImmutableDictionaryWrapper.Count); + Assert.Equal("value", result.MyStringToStringImmutableDictionaryWrapper["key"]); + } + + { + SimpleTestClassWithImmutableStackWrapper result = JsonSerializer.Deserialize(SimpleTestClassWithImmutableStackWrapper.s_json); + + Assert.NotNull(result?.MyStringIImmutableStackWrapper); + Assert.Equal(1, result.MyStringIImmutableStackWrapper.Count()); + Assert.Equal("Hello", result.MyStringIImmutableStackWrapper.First()); + } + + { + SimpleTestClassWithImmutableQueueWrapper result = JsonSerializer.Deserialize(SimpleTestClassWithImmutableQueueWrapper.s_json); + + Assert.NotNull(result?.MyStringIImmutableQueueWrapper); + Assert.Equal(1, result.MyStringIImmutableQueueWrapper.Count()); + Assert.Equal("Hello", result.MyStringIImmutableQueueWrapper.First()); + } } } }