diff --git a/src/System.Text.Json/src/System.Text.Json.csproj b/src/System.Text.Json/src/System.Text.Json.csproj index 23b7672de0bc..d111d0060ef3 100644 --- a/src/System.Text.Json/src/System.Text.Json.csproj +++ b/src/System.Text.Json/src/System.Text.Json.csproj @@ -50,11 +50,7 @@ - - - - @@ -89,7 +85,6 @@ - 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 843d370c089c..e4dad76cb6ac 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 @@ -23,8 +23,5 @@ internal enum ClassType : byte Enumerable = 0x8, // IDictionary Dictionary = 0x10, - // Is deserialized by passing a IDictionary to its constructor - // i.e. immutable dictionaries, Hashtable, SortedList, - IDictionaryConstructible = 0x20, } } 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 deleted file mode 100644 index f70199c1fd2b..000000000000 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedDictionaryConverter.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; - -namespace System.Text.Json.Serialization.Converters -{ - internal sealed class DefaultDerivedDictionaryConverter : JsonDictionaryConverter - { - public override object CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options) - { - JsonPropertyInfo collectionPropertyInfo = state.Current.JsonPropertyInfo; - JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(collectionPropertyInfo.ElementType, options); - return elementPropertyInfo.CreateDerivedDictionaryInstance(ref state, collectionPropertyInfo, sourceDictionary); - } - } -} 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 deleted file mode 100644 index 21a05105fc41..000000000000 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultDerivedEnumerableConverter.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; - -namespace System.Text.Json.Serialization.Converters -{ - internal sealed class DefaultDerivedEnumerableConverter : JsonEnumerableConverter - { - public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options) - { - JsonPropertyInfo collectionPropertyInfo = state.Current.JsonPropertyInfo; - JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(collectionPropertyInfo.ElementType, options); - return elementPropertyInfo.CreateDerivedEnumerableInstance(ref state, collectionPropertyInfo, sourceList); - } - } -} 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 deleted file mode 100644 index 46b6bb33ece9..000000000000 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs +++ /dev/null @@ -1,19 +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; - -namespace System.Text.Json.Serialization.Converters -{ - internal sealed class DefaultICollectionConverter : JsonEnumerableConverter - { - public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options) - { - Type enumerableType = state.Current.JsonPropertyInfo.RuntimePropertyType; - Type elementType = state.Current.JsonPropertyInfo.ElementType; - JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementType, options); - return propertyInfo.CreateIEnumerableInstance(ref state, enumerableType, sourceList); - } - } -} 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 deleted file mode 100644 index 2ad5acdb50ca..000000000000 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs +++ /dev/null @@ -1,19 +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; - -namespace System.Text.Json.Serialization.Converters -{ - internal sealed class DefaultIDictionaryConverter : JsonDictionaryConverter - { - public override object CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options) - { - Type dictionaryType = state.Current.JsonPropertyInfo.RuntimePropertyType; - Type elementType = state.Current.JsonPropertyInfo.ElementType; - JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementType, options); - return propertyInfo.CreateIDictionaryInstance(ref state, dictionaryType, sourceDictionary); - } - } -} 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 357a9682170f..3ec77b536347 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 @@ -59,11 +59,14 @@ public static bool IsImmutableDictionary(Type type) public override object CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options) { Type immutableCollectionType = state.Current.JsonPropertyInfo.RuntimePropertyType; - Type elementType = state.Current.GetElementType(); + + JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo; + Type elementType = elementClassInfo.Type; string delegateKey = DefaultImmutableEnumerableConverter.GetDelegateKey(immutableCollectionType, elementType, out _, out _); - JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementType, options); + JsonPropertyInfo propertyInfo = elementClassInfo.PolicyProperty ?? elementClassInfo.CreateRootObject(options); + Debug.Assert(propertyInfo != null); return propertyInfo.CreateImmutableDictionaryInstance(ref state, immutableCollectionType, delegateKey, sourceDictionary, options); } } 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 b774ea9d6c07..296d542fc3a4 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 @@ -102,14 +102,47 @@ public static void RegisterImmutableCollection(Type immutableCollectionType, Typ options.TryAddCreateRangeDelegate(delegateKey, createRangeDelegate); } + public static bool IsImmutableEnumerable(Type type, out bool IsImmutableArray) + { + if (!type.IsGenericType) + { + IsImmutableArray = false; + return false; + } + + switch (type.GetGenericTypeDefinition().FullName) + { + case ImmutableArrayGenericTypeName: + IsImmutableArray = true; + return true; + case ImmutableListGenericTypeName: + case ImmutableListGenericInterfaceTypeName: + case ImmutableStackGenericTypeName: + case ImmutableStackGenericInterfaceTypeName: + case ImmutableQueueGenericTypeName: + case ImmutableQueueGenericInterfaceTypeName: + case ImmutableSortedSetGenericTypeName: + case ImmutableHashSetGenericTypeName: + case ImmutableSetGenericInterfaceTypeName: + IsImmutableArray = false; + return true; + default: + IsImmutableArray = false; + return false; + } + } + public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options) { Type immutableCollectionType = state.Current.JsonPropertyInfo.RuntimePropertyType; - Type elementType = state.Current.GetElementType(); + + JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo; + Type elementType = elementClassInfo.Type; string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out _, out _); - JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementType, options); + JsonPropertyInfo propertyInfo = elementClassInfo.PolicyProperty ?? elementClassInfo.CreateRootObject(options); + Debug.Assert(propertyInfo != null); return propertyInfo.CreateImmutableCollectionInstance(ref state, immutableCollectionType, delegateKey, sourceList, options); } } 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 c71d9b568de9..4ba60eb069d7 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 @@ -10,131 +10,64 @@ namespace System.Text.Json { internal partial class JsonClassInfo { - private void AddPolicyProperty(Type propertyType, JsonSerializerOptions options) + private JsonPropertyInfo AddProperty(Type propertyType, PropertyInfo propertyInfo, Type parentClassType, JsonSerializerOptions options) { - // A policy property is not a real property on a type; instead it leverages the existing converter - // logic and generic support to avoid boxing. It is used with values types and elements from collections and - // dictionaries. Typically it would represent a CLR type such as System.String. - PolicyProperty = AddProperty( - propertyType, - propertyInfo : null, // Not a real property so this is null. - classType : typeof(object), // A dummy type (not used). - options : options); - } - - private JsonPropertyInfo AddProperty(Type propertyType, PropertyInfo propertyInfo, Type classType, JsonSerializerOptions options) - { - JsonPropertyInfo jsonInfo; - - // Get implemented type, if applicable. - // Will return the propertyType itself if it's a non-enumerable, string, natively supported collection, - // or if a custom converter has been provided for the type. - Type implementedType = GetImplementedCollectionType(classType, propertyType, propertyInfo, out JsonConverter converter, options); - - if (implementedType != propertyType) - { - jsonInfo = CreateProperty(implementedType, implementedType, implementedType, propertyInfo, typeof(object), converter, options); - } - else - { - jsonInfo = CreateProperty(propertyType, propertyType, propertyType, propertyInfo, classType, converter, options); - } - - // Convert non-immutable dictionary interfaces to concrete types. - if (IsNativelySupportedCollection(propertyType) && implementedType.IsInterface && jsonInfo.ClassType == ClassType.Dictionary) + bool hasIgnoreAttribute = (JsonPropertyInfo.GetAttribute(propertyInfo) != null); + if (hasIgnoreAttribute) { - JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(jsonInfo.ElementType, options); - - Type newPropertyType = elementPropertyInfo.GetDictionaryConcreteType(); - if (implementedType != newPropertyType) - { - jsonInfo = CreateProperty(propertyType, newPropertyType, implementedType, propertyInfo, classType, converter, options); - } - else - { - jsonInfo = CreateProperty(propertyType, implementedType, implementedType, propertyInfo, classType, converter, options); - } + return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(propertyInfo, options); } - else if (jsonInfo.ClassType == ClassType.Enumerable && - !implementedType.IsArray && - ((IsDeserializedByAssigningFromList(implementedType) && IsNativelySupportedCollection(propertyType)) || IsSetInterface(implementedType))) - { - JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(jsonInfo.ElementType, options); - // Get a runtime type for the implemented property. e.g. ISet -> 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)) - { - jsonInfo = CreateProperty(propertyType, newPropertyType, implementedType, propertyInfo, classType, converter, options); - } - else - { - jsonInfo = CreateProperty(propertyType, implementedType, implementedType, propertyInfo, classType, converter, options); - } - } - else if (propertyType != implementedType) - { - jsonInfo = CreateProperty(propertyType, implementedType, implementedType, propertyInfo, classType, converter, options); - } + ClassType classType = GetClassType( + propertyType, + parentClassType, + propertyInfo, + out Type runtimeType, + out Type elementType, + out Type nullableUnderlyingType, + out _, + out JsonConverter converter, + checkForAddMethod: false, + options); - return jsonInfo; + return CreateProperty( + declaredPropertyType: propertyType, + runtimePropertyType: runtimeType, + propertyInfo, + parentClassType, + collectionElementType: elementType, + nullableUnderlyingType, + converter, + classType, + options); } internal static JsonPropertyInfo CreateProperty( Type declaredPropertyType, Type runtimePropertyType, - Type implementedPropertyType, PropertyInfo propertyInfo, Type parentClassType, + Type collectionElementType, + Type nullableUnderlyingType, JsonConverter converter, + ClassType classType, JsonSerializerOptions options) { - bool hasIgnoreAttribute = (JsonPropertyInfo.GetAttribute(propertyInfo) != null); - if (hasIgnoreAttribute) - { - return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(propertyInfo, options); - } - - // Obtain the custom converter for the property. - if (converter == null) - { - converter = options.DetermineConverterForProperty(parentClassType, runtimePropertyType, propertyInfo); - } + bool treatAsNullable = nullableUnderlyingType != null; // Obtain the type of the JsonPropertyInfo class to construct. Type propertyInfoClassType; - if (runtimePropertyType.IsGenericType && runtimePropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) + + if (treatAsNullable && converter != null) { - if (converter != null) - { - propertyInfoClassType = typeof(JsonPropertyInfoNotNullable<,,,>).MakeGenericType( - parentClassType, - declaredPropertyType, - runtimePropertyType, - runtimePropertyType); - } - else - { - // Attempt to find converter for underlying type. - Type typeToConvert = Nullable.GetUnderlyingType(runtimePropertyType); - converter = options.DetermineConverterForProperty(parentClassType, typeToConvert, propertyInfo); - propertyInfoClassType = typeof(JsonPropertyInfoNullable<,>).MakeGenericType(parentClassType, typeToConvert); - } + propertyInfoClassType = typeof(JsonPropertyInfoNullable<,>).MakeGenericType(parentClassType, nullableUnderlyingType); } else { Type typeToConvert = converter?.TypeToConvert; if (typeToConvert == null) { - if (IsNativelySupportedCollection(declaredPropertyType)) - { - typeToConvert = implementedPropertyType; - } - else - { - typeToConvert = declaredPropertyType; - } + typeToConvert = declaredPropertyType; } // For the covariant case, create JsonPropertyInfoNotNullable. The generic constraints are "where TConverter : TDeclaredProperty". @@ -167,30 +100,15 @@ internal static JsonPropertyInfo CreateProperty( args: null, culture: null); - // Obtain the collection element type. - Type collectionElementType = null; - if (converter == null) - { - switch (GetClassType(runtimePropertyType, options)) - { - case ClassType.Enumerable: - case ClassType.Dictionary: - case ClassType.IDictionaryConstructible: - case ClassType.Unknown: - collectionElementType = GetElementType(runtimePropertyType, parentClassType, propertyInfo, options); - break; - } - } - - // Initialize the JsonPropertyInfo. jsonPropertyInfo.Initialize( parentClassType, declaredPropertyType, runtimePropertyType, - implementedPropertyType, + runtimeClassType: classType, propertyInfo, collectionElementType, converter, + treatAsNullable, options); return jsonPropertyInfo; @@ -198,25 +116,43 @@ internal static JsonPropertyInfo CreateProperty( internal JsonPropertyInfo CreateRootObject(JsonSerializerOptions options) { + JsonConverter converter = options.DetermineConverterForProperty(Type, Type, propertyInfo: null); + return CreateProperty( declaredPropertyType: Type, runtimePropertyType: Type, - implementedPropertyType: Type, propertyInfo: null, parentClassType: Type, - converter: null, - options: options); + ElementType, + Nullable.GetUnderlyingType(Type), + converter, + ClassType, + options); } internal JsonPropertyInfo CreatePolymorphicProperty(JsonPropertyInfo property, Type runtimePropertyType, JsonSerializerOptions options) { + ClassType classType = GetClassType( + runtimePropertyType, + Type, + property.PropertyInfo, + out _, + out Type elementType, + out Type nullableType, + out _, + out JsonConverter converter, + checkForAddMethod: false, + options); + JsonPropertyInfo runtimeProperty = CreateProperty( property.DeclaredPropertyType, runtimePropertyType, - property.ImplementedPropertyType, property.PropertyInfo, parentClassType: Type, - converter: null, + collectionElementType: elementType, + nullableType, + converter, + classType, options: options); property.CopyRuntimeSettingsTo(runtimeProperty); 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 188ac7dc332a..71bd2d5b97fa 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 @@ -9,7 +9,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text.Json.Serialization; -using System.Text.Json.Serialization.Converters; namespace System.Text.Json { @@ -37,7 +36,6 @@ internal sealed partial class JsonClassInfo public delegate object ConstructorDelegate(); public ConstructorDelegate CreateObject { get; private set; } - public ConstructorDelegate CreateConcreteDictionary { get; private set; } public ClassType ClassType { get; private set; } @@ -60,8 +58,7 @@ public JsonClassInfo ElementClassInfo if (_elementClassInfo == null && ElementType != null) { Debug.Assert(ClassType == ClassType.Enumerable || - ClassType == ClassType.Dictionary || - ClassType == ClassType.IDictionaryConstructible); + ClassType == ClassType.Dictionary); _elementClassInfo = Options.GetOrAddClass(ElementType); } @@ -116,15 +113,26 @@ public JsonClassInfo(Type type, JsonSerializerOptions options) { Type = type; Options = options; - ClassType = GetClassType(type, options); - CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); + ClassType = GetClassType( + type, + parentClassType: type, + propertyInfo: null, + out Type runtimeType, + out Type elementType, + out Type nullableUnderlyingType, + out MethodInfo addMethod, + out JsonConverter converter, + checkForAddMethod: true, + options); // Ignore properties on enumerable. switch (ClassType) { case ClassType.Object: { + CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); + PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Dictionary cache = CreatePropertyCache(properties.Length); @@ -189,47 +197,64 @@ 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); - - Type objectType; - if (IsNativelySupportedCollection(type)) - { - // Use the type from the property policy to get any late-bound concrete types (from an interface like IDictionary). - objectType = PolicyProperty.RuntimePropertyType; - } - else - { - // We need to create the declared instance for types implementing natively supported collections. - objectType = PolicyProperty.DeclaredPropertyType; - } - - CreateObject = options.MemberAccessorStrategy.CreateConstructor(objectType); - - ElementType = GetElementType(type, parentType: null, memberInfo: null, options: options); + ElementType = elementType; + AddItemToObject = addMethod; + + // A policy property is not a real property on a type; instead it leverages the existing converter + // logic and generic support to avoid boxing. It is used with values types, elements from collections and + // dictionaries, and collections themselves. Typically it would represent a CLR type such as System.String. + PolicyProperty = CreateProperty( + declaredPropertyType: type, + runtimePropertyType: runtimeType, + propertyInfo: null, // Not a real property so this is null. + parentClassType: typeof(object), + collectionElementType: elementType, + nullableUnderlyingType, + converter: null, + ClassType, + options); + + CreateObject = options.MemberAccessorStrategy.CreateConstructor(PolicyProperty.RuntimePropertyType); } break; - case ClassType.IDictionaryConstructible: + case ClassType.Value: { - // Add a single property that maps to the class type so we can have policies applied. - AddPolicyProperty(type, options); - - ElementType = GetElementType(type, parentType: null, memberInfo: null, options: options); + CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); - CreateConcreteDictionary = options.MemberAccessorStrategy.CreateConstructor( - typeof(Dictionary<,>).MakeGenericType(typeof(string), ElementType)); - - CreateObject = options.MemberAccessorStrategy.CreateConstructor(PolicyProperty.DeclaredPropertyType); + // Add a single property that maps to the class type so we can have policies applied. + //AddPolicyPropertyForValue(type, options); + PolicyProperty = CreateProperty( + declaredPropertyType: type, + runtimePropertyType: runtimeType, + propertyInfo: null, // Not a real property so this is null. + parentClassType: typeof(object), + collectionElementType: null, + nullableUnderlyingType, + converter, + ClassType, + options); } break; - case ClassType.Value: - // Add a single property that maps to the class type so we can have policies applied. - AddPolicyProperty(type, options); - break; case ClassType.Unknown: - // Add a single property that maps to the class type so we can have policies applied. - AddPolicyProperty(type, options); - PropertyCache = new Dictionary(); - PropertyCacheArray = Array.Empty(); + { + CreateObject = options.MemberAccessorStrategy.CreateConstructor(type); + + // Add a single property that maps to the class type so we can have policies applied. + //AddPolicyPropertyForValue(type, options); + PolicyProperty = CreateProperty( + declaredPropertyType: type, + runtimePropertyType: runtimeType, + propertyInfo: null, // Not a real property so this is null. + parentClassType: typeof(object), + collectionElementType: null, + nullableUnderlyingType, + converter, + ClassType, + options); + + PropertyCache = new Dictionary(); + PropertyCacheArray = Array.Empty(); + } break; default: Debug.Fail($"Unexpected class type: {ClassType}"); @@ -406,6 +431,8 @@ private Dictionary CreatePropertyCache(int capacity) public JsonPropertyInfo PolicyProperty { get; private set; } + public MethodInfo AddItemToObject { get; private set; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool TryIsPropertyRefEqual(in PropertyRef propertyRef, ReadOnlySpan propertyName, ulong key, ref JsonPropertyInfo info) { @@ -513,105 +540,207 @@ public static ulong GetKey(ReadOnlySpan propertyName) return key; } - // Return the element type of the IEnumerable or return null if not an IEnumerable. - public static Type GetElementType(Type propertyType, Type parentType, MemberInfo memberInfo, JsonSerializerOptions options) + // This method gets the runtime information for a given type or property. + // The runtime information consists of the following: + // - class type, + // - runtime type, + // - element type (if the type is a collection), + // - the underlying type (if the type is nullable type e.g. int?), + // - the "add" method (if the type is a non-dictionary collection which doesn't implement IList + // e.g. typeof(Stack), where we retrieve the void Push(string) method), and + // - the converter (either native or custom), if one exists. + public static ClassType GetClassType( + Type type, + Type parentClassType, + PropertyInfo propertyInfo, + out Type runtimeType, + out Type elementType, + out Type nullableUnderlyingType, + out MethodInfo addMethod, + out JsonConverter converter, + bool checkForAddMethod, + JsonSerializerOptions options) { - // We want to handle as the implemented collection type, if applicable. - Type implementedType = GetImplementedCollectionType(parentType, propertyType, propertyInfo: null, out _, options); + Debug.Assert(type != null); - if (!typeof(IEnumerable).IsAssignableFrom(implementedType)) - { - return null; - } + runtimeType = type; - // Check for Array. - Type elementType = implementedType.GetElementType(); - if (elementType != null) - { - return elementType; - } + nullableUnderlyingType = Nullable.GetUnderlyingType(type); - // Check for Dictionary or IEnumerable - if (implementedType.IsGenericType) + // Type is nullable e.g. typeof(int?). + if (nullableUnderlyingType != null) { - Type[] args = implementedType.GetGenericArguments(); - ClassType classType = GetClassType(implementedType, options); + // Check if there's a converter for this nullable type, e.g. do we have a converter that implements + // JsonConverter if the type is typeof(int?)? + converter = options.DetermineConverterForProperty(parentClassType, type, propertyInfo); - if ((classType == ClassType.Dictionary || classType == ClassType.IDictionaryConstructible) && - args.Length >= 2 && // It is >= 2 in case there is a IDictionary. - args[0].UnderlyingSystemType == typeof(string)) + if (converter == null) { - return args[1]; + // No converter. We'll check below if there's a converter for the non-nullable type e.g. + // one that implements JsonConverter, given the type is typeof(int?). + type = nullableUnderlyingType; } - - if (classType == ClassType.Enumerable && args.Length >= 1) // It is >= 1 in case there is an IEnumerable. + else { - return args[0]; + elementType = default; + addMethod = default; + // Don't treat the type as a Nullable when creating the property info later on, since we have a converter for it. + nullableUnderlyingType = default; + return ClassType.Value; } } - if (implementedType.IsAssignableFrom(typeof(IList)) || - implementedType.IsAssignableFrom(typeof(IDictionary)) || - IsDeserializedByConstructingWithIList(implementedType) || - IsDeserializedByConstructingWithIDictionary(implementedType)) + converter = options.DetermineConverterForProperty(parentClassType, type, propertyInfo); + + if (converter != null) { - return typeof(object); + elementType = default; + addMethod = default; + return type == typeof(object) ? ClassType.Unknown : ClassType.Value; } - throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(propertyType, parentType, memberInfo); - } - - public static ClassType GetClassType(Type type, JsonSerializerOptions options) - { - Debug.Assert(type != null); - - // We want to handle as the implemented collection type, if applicable. - Type implementedType = GetImplementedCollectionType(typeof(object), type, propertyInfo: null, out _, options); + runtimeType = type; - if (implementedType.IsGenericType && implementedType.GetGenericTypeDefinition() == typeof(Nullable<>)) + if (!(typeof(IEnumerable)).IsAssignableFrom(type)) { - implementedType = Nullable.GetUnderlyingType(implementedType); + elementType = null; + addMethod = default; + return ClassType.Object; } - if (implementedType == typeof(object)) + if (type.IsArray) { - return ClassType.Unknown; + elementType = type.GetElementType(); + addMethod = default; + return ClassType.Enumerable; } - if (options.HasConverter(implementedType)) + if (type.FullName.StartsWith("System.Collections.Generic.IEnumerable`1")) { - return ClassType.Value; + elementType = type.GetGenericArguments()[0]; + runtimeType = typeof(List<>).MakeGenericType(elementType); + addMethod = default; + return ClassType.Enumerable; + } + else if (type.FullName.StartsWith("System.Collections.Generic.IDictionary`2") || + type.FullName.StartsWith("System.Collections.Generic.IReadOnlyDictionary`2")) + { + Type[] genericTypes = type.GetGenericArguments(); + + elementType = genericTypes[1]; + runtimeType = typeof(Dictionary<,>).MakeGenericType(genericTypes[0], elementType); + addMethod = default; + return ClassType.Dictionary; } - if (DefaultImmutableDictionaryConverter.IsImmutableDictionary(implementedType) || - IsDeserializedByConstructingWithIDictionary(implementedType)) { - return ClassType.IDictionaryConstructible; + Type genericIDictionaryType = type.GetInterface("System.Collections.Generic.IDictionary`2") ?? type.GetInterface("System.Collections.Generic.IReadOnlyDictionary`2"); + if (genericIDictionaryType != null) + { + Type[] genericTypes = genericIDictionaryType.GetGenericArguments(); + elementType = genericTypes[1]; + addMethod = default; + + if (type.IsInterface) + { + Type concreteDictionaryType = typeof(Dictionary<,>).MakeGenericType(genericTypes[0], genericTypes[1]); + + if (type.IsAssignableFrom(concreteDictionaryType)) + { + runtimeType = concreteDictionaryType; + } + } + + return ClassType.Dictionary; + } } - if (typeof(IDictionary).IsAssignableFrom(implementedType) || IsDictionaryClassType(implementedType)) + if (typeof(IDictionary).IsAssignableFrom(type)) { - // Special case for immutable dictionaries - if (type != implementedType && !IsNativelySupportedCollection(type)) + elementType = typeof(object); + addMethod = default; + + if (type.IsInterface) { - return ClassType.IDictionaryConstructible; + Type concreteDictionaryType = typeof(Dictionary); + + if (type.IsAssignableFrom(concreteDictionaryType)) + { + runtimeType = concreteDictionaryType; + } } return ClassType.Dictionary; } - if (typeof(IEnumerable).IsAssignableFrom(implementedType)) { - return ClassType.Enumerable; + Type genericIEnumerableType = type.GetInterface("System.Collections.Generic.IEnumerable`1"); + + if (genericIEnumerableType != null) + { + elementType = genericIEnumerableType.GetGenericArguments()[0]; + } + else + { + elementType = typeof(object); + } } - return ClassType.Object; - } + if (typeof(IList).IsAssignableFrom(type)) + { + addMethod = default; - public static bool IsDictionaryClassType(Type type) - { - return (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IDictionary<,>) || - type.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>))); + if (type.IsInterface) + { + Type concreteListType = typeof(List<>).MakeGenericType(elementType); + if (type.IsAssignableFrom(concreteListType)) + { + runtimeType = concreteListType; + } + } + } + else if (type.IsInterface) + { + addMethod = default; + + Type concreteType = typeof(List<>).MakeGenericType(elementType); + if (type.IsAssignableFrom(concreteType)) + { + runtimeType = concreteType; + } + else + { + concreteType = typeof(HashSet<>).MakeGenericType(elementType); + if (type.IsAssignableFrom(concreteType)) + { + runtimeType = concreteType; + } + } + } + else + { + addMethod = default; + + if (checkForAddMethod) + { + Type genericICollectionType = type.GetInterface("System.Collections.Generic.ICollection`1"); + if (genericICollectionType != null) + { + addMethod = genericICollectionType.GetMethod("Add"); + } + else + { + // Non-immutable stack or queue. + MethodInfo methodInfo = type.GetMethod("Push") ?? type.GetMethod("Enqueue"); + if (methodInfo?.ReturnType == typeof(void)) + { + addMethod = methodInfo; + } + } + } + } + + return ClassType.Enumerable; } } } 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 dee80e324ae4..7aa1019d9773 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 @@ -14,12 +14,8 @@ namespace System.Text.Json internal abstract class JsonPropertyInfo { // Cache the converters so they don't get created for every enumerable property. - private static readonly JsonEnumerableConverter s_jsonDerivedEnumerableConverter = new DefaultDerivedEnumerableConverter(); private static readonly JsonEnumerableConverter s_jsonArrayConverter = new DefaultArrayConverter(); - private static readonly JsonEnumerableConverter s_jsonICollectionConverter = new DefaultICollectionConverter(); private static readonly JsonEnumerableConverter s_jsonImmutableEnumerableConverter = new DefaultImmutableEnumerableConverter(); - private static readonly JsonDictionaryConverter s_jsonDerivedDictionaryConverter = new DefaultDerivedDictionaryConverter(); - private static readonly JsonDictionaryConverter s_jsonIDictionaryConverter = new DefaultIDictionaryConverter(); private static readonly JsonDictionaryConverter s_jsonImmutableDictionaryConverter = new DefaultImmutableDictionaryConverter(); public static readonly JsonPropertyInfo s_missingProperty = GetMissingProperty(); @@ -29,6 +25,7 @@ internal abstract class JsonPropertyInfo private JsonClassInfo _declaredTypeClassInfo; public bool CanBeNull { get; private set; } + public bool IsImmutableArray { get; private set; } public ClassType ClassType; @@ -54,13 +51,7 @@ public void CopyRuntimeSettingsTo(JsonPropertyInfo other) public abstract IList CreateConverterList(); - public abstract IEnumerable CreateDerivedEnumerableInstance(ref ReadStack state, JsonPropertyInfo collectionPropertyInfo, IList sourceList); - - public abstract object CreateDerivedDictionaryInstance(ref ReadStack state, JsonPropertyInfo collectionPropertyInfo, IDictionary sourceDictionary); - - public abstract IEnumerable CreateIEnumerableInstance(ref ReadStack state, Type parentType, IList sourceList); - - public abstract IDictionary CreateIDictionaryInstance(ref ReadStack state, Type parentType, IDictionary sourceDictionary); + public abstract IDictionary CreateConverterDictionary(); public abstract IEnumerable CreateImmutableCollectionInstance(ref ReadStack state, Type collectionType, string delegateKey, IList sourceList, JsonSerializerOptions options); @@ -83,8 +74,6 @@ public static JsonPropertyInfo CreateIgnoredPropertyPlaceholder(PropertyInfo pro public Type DeclaredPropertyType { get; private set; } - public Type ImplementedPropertyType { get; private set; } - private void DeterminePropertyName() { if (PropertyInfo == null) @@ -132,9 +121,7 @@ private void DeterminePropertyName() private void DetermineSerializationCapabilities() { - if (ClassType != ClassType.Enumerable && - ClassType != ClassType.Dictionary && - ClassType != ClassType.IDictionaryConstructible) + if ((ClassType & (ClassType.Enumerable | ClassType.Dictionary)) == 0) { // We serialize if there is a getter + not ignoring readonly properties. ShouldSerialize = HasGetter && (HasSetter || !Options.IgnoreReadOnlyProperties); @@ -162,54 +149,17 @@ private void DetermineSerializationCapabilities() EnumerableConverter = s_jsonArrayConverter; } - else if (ClassType == ClassType.IDictionaryConstructible) + else if (ClassType == ClassType.Dictionary && DefaultImmutableDictionaryConverter.IsImmutableDictionary(RuntimePropertyType)) { - // Natively supported type. - if (DeclaredPropertyType == ImplementedPropertyType) - { - if (RuntimePropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName)) - { - DefaultImmutableDictionaryConverter.RegisterImmutableDictionary( - RuntimePropertyType, JsonClassInfo.GetElementType(RuntimePropertyType, ParentClassType, PropertyInfo, Options), Options); - - DictionaryConverter = s_jsonImmutableDictionaryConverter; - } - else if (JsonClassInfo.IsDeserializedByConstructingWithIDictionary(RuntimePropertyType)) - { - DictionaryConverter = s_jsonIDictionaryConverter; - } - } - // Type that implements a type with ClassType IDictionaryConstructible. - else - { - DictionaryConverter = s_jsonDerivedDictionaryConverter; - } + DefaultImmutableDictionaryConverter.RegisterImmutableDictionary(RuntimePropertyType, ElementType, Options); + DictionaryConverter = s_jsonImmutableDictionaryConverter; } - else if (ClassType == ClassType.Enumerable) + else if (ClassType == ClassType.Enumerable && DefaultImmutableEnumerableConverter.IsImmutableEnumerable(RuntimePropertyType, out bool isImmutableArray)) { - // Else if it's an implementing type whose runtime type is not assignable to IList. - if (DeclaredPropertyType != ImplementedPropertyType && - (!typeof(IList).IsAssignableFrom(RuntimePropertyType) || - ImplementedPropertyType == typeof(ArrayList) || - ImplementedPropertyType == typeof(IList))) - { - EnumerableConverter = s_jsonDerivedEnumerableConverter; - } - else if (JsonClassInfo.IsDeserializedByConstructingWithIList(RuntimePropertyType) || - (!typeof(IList).IsAssignableFrom(RuntimePropertyType) && - JsonClassInfo.HasConstructorThatTakesGenericIEnumerable(RuntimePropertyType, Options))) - { - EnumerableConverter = s_jsonICollectionConverter; - } - else if (RuntimePropertyType.IsGenericType && - RuntimePropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName) && - RuntimePropertyType.GetGenericArguments().Length == 1) - { - DefaultImmutableEnumerableConverter.RegisterImmutableCollection(RuntimePropertyType, - JsonClassInfo.GetElementType(RuntimePropertyType, ParentClassType, PropertyInfo, Options), Options); - EnumerableConverter = s_jsonImmutableEnumerableConverter; - } + DefaultImmutableEnumerableConverter.RegisterImmutableCollection(RuntimePropertyType, ElementType, Options); + EnumerableConverter = s_jsonImmutableEnumerableConverter; + IsImmutableArray = isImmutableArray; } } } @@ -230,8 +180,7 @@ public JsonClassInfo ElementClassInfo if (_elementClassInfo == null && ElementType != null) { Debug.Assert(ClassType == ClassType.Enumerable || - ClassType == ClassType.Dictionary || - ClassType == ClassType.IDictionaryConstructible); + ClassType == ClassType.Dictionary); _elementClassInfo = Options.GetOrAddClass(ElementType); } @@ -256,8 +205,6 @@ public static TAttribute GetAttribute(PropertyInfo propertyInfo) whe public abstract Type GetDictionaryConcreteType(); - public abstract Type GetConcreteType(Type type); - public virtual void GetPolicies() { DetermineSerializationCapabilities(); @@ -276,52 +223,47 @@ public virtual void Initialize( Type parentClassType, Type declaredPropertyType, Type runtimePropertyType, - Type implementedPropertyType, + ClassType runtimeClassType, PropertyInfo propertyInfo, Type elementType, JsonConverter converter, + bool treatAsNullable, JsonSerializerOptions options) { ParentClassType = parentClassType; DeclaredPropertyType = declaredPropertyType; RuntimePropertyType = runtimePropertyType; - ImplementedPropertyType = implementedPropertyType; + ClassType = runtimeClassType; PropertyInfo = propertyInfo; ElementType = elementType; Options = options; - IsNullableType = runtimePropertyType.IsGenericType && runtimePropertyType.GetGenericTypeDefinition() == typeof(Nullable<>); - CanBeNull = IsNullableType || !runtimePropertyType.IsValueType; + CanBeNull = treatAsNullable || !runtimePropertyType.IsValueType; if (converter != null) { ConverterBase = converter; HasInternalConverter = (converter.GetType().Assembly == GetType().Assembly); - - // Avoid calling GetClassType since it will re-ask if there is a converter which is slow. - if (runtimePropertyType == typeof(object)) - { - ClassType = ClassType.Unknown; - } - else - { - ClassType = ClassType.Value; - } - } - // Special case for immutable collections. - else if (declaredPropertyType != implementedPropertyType && !JsonClassInfo.IsNativelySupportedCollection(declaredPropertyType)) - { - ClassType = JsonClassInfo.GetClassType(declaredPropertyType, options); - } - else - { - ClassType = JsonClassInfo.GetClassType(runtimePropertyType, options); } } - public bool IgnoreNullValues { get; private set; } + public abstract bool TryCreateEnumerableAddMethod(object target, out object addMethodDelegate); - public bool IsNullableType { get; private set; } + public abstract object CreateEnumerableAddMethod(MethodInfo addMethod, object target); + + public abstract void AddObjectToEnumerableWithReflection(object addMethodDelegate, object value); + + public abstract void AddObjectToParentEnumerable(object addMethodDelegate, object value); + + public abstract void AddObjectToDictionary(object target, string key, object value); + + public abstract void AddObjectToParentDictionary(object target, string key, object value); + + public abstract bool CanPopulateDictionary(object target); + + public abstract bool ParentDictionaryCanBePopulated(object target); + + public bool IgnoreNullValues { get; private set; } public bool IsPropertyPolicy { get; protected set; } 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 7b623d80363e..723fe8311cc7 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 @@ -18,25 +18,33 @@ internal abstract class JsonPropertyInfoCommon Get { get; private set; } public Action Set { get; private set; } + public Action AddItemToEnumerable { get; private set; } + public JsonConverter Converter { get; internal set; } public override void Initialize( Type parentClassType, Type declaredPropertyType, Type runtimePropertyType, - Type implementedPropertyType, + ClassType runtimeClassType, PropertyInfo propertyInfo, Type elementType, JsonConverter converter, + bool treatAsNullable, JsonSerializerOptions options) { - base.Initialize(parentClassType, declaredPropertyType, runtimePropertyType, implementedPropertyType, propertyInfo, elementType, converter, options); - - if (propertyInfo != null && - // We only want to get the getter and setter if we are going to use them. - // If the declared type is not the property info type, then we are just - // getting metadata on how best to (de)serialize derived types. - declaredPropertyType == propertyInfo.PropertyType) + base.Initialize( + parentClassType, + declaredPropertyType, + runtimePropertyType, + runtimeClassType, + propertyInfo, + elementType, + converter, + treatAsNullable, + options); + + if (propertyInfo != null) { if (propertyInfo.GetMethod?.IsPublic == true) { @@ -97,180 +105,101 @@ public override void SetValueAsObject(object obj, object value) } } - public override IList CreateConverterList() - { - return new List(); - } + private JsonPropertyInfo _elementPropertyInfo; - public override Type GetConcreteType(Type parentType) + private void SetPropertyInfoForObjectElement() { - if (JsonClassInfo.IsDeserializedByAssigningFromList(parentType)) - { - return typeof(List); - } - else if (JsonClassInfo.IsSetInterface(parentType)) + if (_elementPropertyInfo == null && ElementClassInfo.PolicyProperty == null) { - return typeof(HashSet); + _elementPropertyInfo = ElementClassInfo.CreateRootObject(Options); } + } - return parentType; + public override bool TryCreateEnumerableAddMethod(object target, out object addMethodDelegate) + { + SetPropertyInfoForObjectElement(); + Debug.Assert((_elementPropertyInfo ?? ElementClassInfo.PolicyProperty) != null); + + addMethodDelegate = (_elementPropertyInfo ?? ElementClassInfo.PolicyProperty).CreateEnumerableAddMethod(RuntimeClassInfo.AddItemToObject, target); + return addMethodDelegate != null; } - public override IEnumerable CreateDerivedEnumerableInstance(ref ReadStack state, JsonPropertyInfo collectionPropertyInfo, IList sourceList) + public override object CreateEnumerableAddMethod(MethodInfo addMethod, object target) { - // Implementing types that don't have default constructors are not supported for deserialization. - if (collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject == null) + if (target is ICollection collection && collection.IsReadOnly) { - throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( - collectionPropertyInfo.DeclaredPropertyType, - collectionPropertyInfo.ParentClassType, - collectionPropertyInfo.PropertyInfo); + return null; } - object instance = collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject(); + return Options.MemberAccessorStrategy.CreateAddDelegate(addMethod, target); + } - if (instance is IList instanceOfIList && !instanceOfIList.IsReadOnly) - { - foreach (object item in sourceList) - { - instanceOfIList.Add(item); - } + public override void AddObjectToEnumerableWithReflection(object addMethodDelegate, object value) + { + Debug.Assert((_elementPropertyInfo ?? ElementClassInfo.PolicyProperty) != null); + (_elementPropertyInfo ?? ElementClassInfo.PolicyProperty).AddObjectToParentEnumerable(addMethodDelegate, value); + } - return instanceOfIList; - } - else if (instance is ICollection instanceOfICollection && !instanceOfICollection.IsReadOnly) - { - foreach (TDeclaredProperty item in sourceList) - { - instanceOfICollection.Add(item); - } + public override void AddObjectToParentEnumerable(object addMethodDelegate, object value) + { + ((Action)addMethodDelegate)((TDeclaredProperty)value); + } - return instanceOfICollection; - } - else if (instance is Stack instanceOfStack) + public override void AddObjectToDictionary(object target, string key, object value) + { + Debug.Assert((_elementPropertyInfo ?? ElementClassInfo.PolicyProperty) != null); + (_elementPropertyInfo ?? ElementClassInfo.PolicyProperty).AddObjectToParentDictionary(target, key, value); + } + public override void AddObjectToParentDictionary(object target, string key, object value) + { + if (target is IDictionary genericDict) { - foreach (TDeclaredProperty item in sourceList) - { - instanceOfStack.Push(item); - } - - return instanceOfStack; + Debug.Assert(!genericDict.IsReadOnly); + genericDict[key] = (TDeclaredProperty)value; } - else if (instance is Queue instanceOfQueue) + else { - foreach (TDeclaredProperty item in sourceList) - { - instanceOfQueue.Enqueue(item); - } - - return instanceOfQueue; + throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(target.GetType(), parentType: null, memberInfo: null); } + } - // TODO (https://github.com/dotnet/corefx/issues/40479): - // Use reflection to support types implementing Stack or Queue. - throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( - collectionPropertyInfo.DeclaredPropertyType, - collectionPropertyInfo.ParentClassType, - collectionPropertyInfo.PropertyInfo); + public override bool CanPopulateDictionary(object target) + { + SetPropertyInfoForObjectElement(); + Debug.Assert((_elementPropertyInfo ?? ElementClassInfo.PolicyProperty) != null); + return (_elementPropertyInfo ?? ElementClassInfo.PolicyProperty).ParentDictionaryCanBePopulated(target); } - public override object CreateDerivedDictionaryInstance(ref ReadStack state, JsonPropertyInfo collectionPropertyInfo, IDictionary sourceDictionary) + public override bool ParentDictionaryCanBePopulated(object target) { - // Implementing types that don't have default constructors are not supported for deserialization. - if (collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject == null) + if (target is IDictionary genericDict && !genericDict.IsReadOnly) { - throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( - collectionPropertyInfo.DeclaredPropertyType, - collectionPropertyInfo.ParentClassType, - collectionPropertyInfo.PropertyInfo); + return true; } - - object instance = collectionPropertyInfo.DeclaredTypeClassInfo.CreateObject(); - - if (instance is IDictionary instanceOfIDictionary && !instanceOfIDictionary.IsReadOnly) + else if (target is IDictionary dict && !dict.IsReadOnly) { - foreach (DictionaryEntry entry in sourceDictionary) - { - instanceOfIDictionary.Add((string)entry.Key, entry.Value); - } + Type genericDictType = target.GetType().GetInterface("System.Collections.Generic.IDictionary`2") ?? + target.GetType().GetInterface("System.Collections.Generic.IReadOnlyDictionary`2"); - return instanceOfIDictionary; - } - else if (instance is IDictionary instanceOfGenericIDictionary && !instanceOfGenericIDictionary.IsReadOnly) - { - foreach (DictionaryEntry entry in sourceDictionary) + if (genericDictType != null && genericDictType.GetGenericArguments()[0] != typeof(string)) { - instanceOfGenericIDictionary.Add((string)entry.Key, (TDeclaredProperty)entry.Value); + return false; } - return instanceOfGenericIDictionary; + return true; } - // TODO (https://github.com/dotnet/corefx/issues/40479): - // Use reflection to support types implementing SortedList and maybe immutable dictionaries. - - // Types implementing SortedList and immutable dictionaries will fail here. - throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( - collectionPropertyInfo.DeclaredPropertyType, - collectionPropertyInfo.ParentClassType, - collectionPropertyInfo.PropertyInfo); + return false; } - public override IEnumerable CreateIEnumerableInstance(ref ReadStack state, Type parentType, IList sourceList) + public override IList CreateConverterList() { - if (parentType.IsGenericType) - { - Type genericTypeDefinition = parentType.GetGenericTypeDefinition(); - IEnumerable items = CreateGenericTDeclaredPropertyIEnumerable(sourceList); - - if (genericTypeDefinition == typeof(Stack<>)) - { - return new Stack(items); - } - else if (genericTypeDefinition == typeof(Queue<>)) - { - return new Queue(items); - } - else if (genericTypeDefinition == typeof(HashSet<>)) - { - return new HashSet(items); - } - else if (genericTypeDefinition == typeof(LinkedList<>)) - { - return new LinkedList(items); - } - else if (genericTypeDefinition == typeof(SortedSet<>)) - { - return new SortedSet(items); - } - - return (IEnumerable)Activator.CreateInstance(parentType, items); - } - else - { - if (parentType == typeof(ArrayList)) - { - return new ArrayList(sourceList); - } - // Stack and Queue go into this condition, until we support with reflection. - else - { - return (IEnumerable)Activator.CreateInstance(parentType, sourceList); - } - } + return new List(); } - public override IDictionary CreateIDictionaryInstance(ref ReadStack state, Type parentType, IDictionary sourceDictionary) + public override IDictionary CreateConverterDictionary() { - if (parentType.FullName == JsonClassInfo.HashtableTypeName) - { - return new Hashtable(sourceDictionary); - } - // SortedList goes into this condition, unless we add a ref to System.Collections.NonGeneric. - else - { - return (IDictionary)Activator.CreateInstance(parentType, sourceDictionary); - } + return new Dictionary(); } // Creates an IEnumerable and populates it with the items in the @@ -304,13 +233,5 @@ public override IDictionary CreateImmutableDictionaryInstance(ref ReadStack stat return collection; } - - private IEnumerable CreateGenericTDeclaredPropertyIEnumerable(IList sourceList) - { - foreach (object item in sourceList) - { - yield return (TDeclaredProperty)item; - } - } } } 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 eb90e964660f..6dcc33f24ded 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 @@ -40,7 +40,7 @@ protected override void OnReadEnumerable(ref ReadStack state, ref Utf8JsonReader ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType); } - if (state.Current.KeyName == null && state.Current.IsProcessingDictionaryOrIDictionaryConstructible()) + if (state.Current.KeyName == null && state.Current.IsProcessingDictionary()) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType); return; @@ -101,6 +101,7 @@ protected override void OnWriteEnumerable(ref WriteStackFrame current, Utf8JsonW Debug.Assert(current.CollectionEnumerator != null); TConverter value; + if (current.CollectionEnumerator is IEnumerator enumerator) { // Avoid boxing for strongly-typed enumerators such as returned from IList. 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 4216239c0004..3b2f63c467df 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 @@ -42,7 +42,7 @@ protected override void OnReadEnumerable(ref ReadStack state, ref Utf8JsonReader ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType); } - if (state.Current.KeyName == null && state.Current.IsProcessingDictionaryOrIDictionaryConstructible()) + if (state.Current.KeyName == null && state.Current.IsProcessingDictionary()) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType); return; 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 724578ed7efd..f0abea141716 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 @@ -91,10 +91,20 @@ protected override void OnWriteDictionary(ref WriteStackFrame current, Utf8JsonW key = enumerator.Current.Key; value = enumerator.Current.Value; } - else if (current.IsIDictionaryConstructible || current.IsIDictionaryConstructibleProperty) + else { - key = (string)((DictionaryEntry)current.CollectionEnumerator.Current).Key; - value = (TProperty?)((DictionaryEntry)current.CollectionEnumerator.Current).Value; + if (((DictionaryEntry)current.CollectionEnumerator.Current).Key is string keyAsString) + { + key = keyAsString; + value = (TProperty?)((DictionaryEntry)current.CollectionEnumerator.Current).Value; + } + else + { + throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( + current.JsonPropertyInfo.DeclaredPropertyType, + current.JsonPropertyInfo.ParentClassType, + current.JsonPropertyInfo.PropertyInfo); + } } Debug.Assert(key != null); 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 c01ab827cf56..d5f08b158009 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 @@ -5,16 +5,14 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Text.Json.Serialization.Converters; namespace System.Text.Json { public static partial class JsonSerializer { - private static void HandleStartArray( - JsonSerializerOptions options, - ref Utf8JsonReader reader, - ref ReadStack state) + private static void HandleStartArray(JsonSerializerOptions options, ref ReadStack state) { if (state.Current.SkipProperty) { @@ -58,19 +56,20 @@ private static void HandleStartArray( Debug.Assert(state.Current.JsonClassInfo.ClassType != ClassType.Value); // Set or replace the existing enumerable value. - object value = ReadStackFrame.CreateEnumerableValue(ref reader, ref state); + object value = ReadStackFrame.CreateEnumerableValue(ref state); // If value is not null, then we don't have a converter so apply the value. if (value != null) { + state.Current.DetermineEnumerablePopulationStrategy(value); + if (state.Current.ReturnValue != null) { state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); } else { - // Primitive arrays being returned without object - state.Current.SetReturnValue(value); + state.Current.ReturnValue = value; } } } @@ -133,7 +132,6 @@ private static bool HandleEndArray( } ApplyObjectToEnumerable(value, ref state); - return false; } @@ -153,56 +151,80 @@ internal static void ApplyObjectToEnumerable( } else { - if (!(state.Current.ReturnValue is IList list)) + if (state.Current.AddObjectToEnumerable == null) { - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(value.GetType()); - return; + if (state.Current.ReturnValue is IList list) + { + list.Add(value); + } + else + { + ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(value.GetType()); + return; + } + } + else + { + state.Current.JsonPropertyInfo.AddObjectToEnumerableWithReflection(state.Current.AddObjectToEnumerable, value); } - list.Add(value); } } else if (!setPropertyDirectly && state.Current.IsProcessingProperty(ClassType.Enumerable)) { Debug.Assert(state.Current.JsonPropertyInfo != null); Debug.Assert(state.Current.ReturnValue != null); + if (state.Current.TempEnumerableValues != null) { state.Current.TempEnumerableValues.Add(value); } else { - IList list = (IList)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue); - if (list == null || + JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; + + object currentEnumerable = jsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue); + if (currentEnumerable == null || // ImmutableArray is a struct, so default value won't be null. - state.Current.JsonPropertyInfo.RuntimePropertyType.FullName.StartsWith(DefaultImmutableEnumerableConverter.ImmutableArrayGenericTypeName)) + jsonPropertyInfo.IsImmutableArray) + { + jsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); + } + else if (state.Current.AddObjectToEnumerable == null) { - state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); + ((IList)currentEnumerable).Add(value); } else { - list.Add(value); + jsonPropertyInfo.AddObjectToEnumerableWithReflection(state.Current.AddObjectToEnumerable, value); } } - } - else if (state.Current.IsProcessingObject(ClassType.Dictionary) || - (state.Current.IsProcessingProperty(ClassType.Dictionary) && !setPropertyDirectly)) - { - Debug.Assert(state.Current.ReturnValue != null); - IDictionary dictionary = (IDictionary)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue); - string key = state.Current.KeyName; - Debug.Assert(!string.IsNullOrEmpty(key)); - dictionary[key] = value; } - else if (state.Current.IsProcessingObject(ClassType.IDictionaryConstructible) || - (state.Current.IsProcessingProperty(ClassType.IDictionaryConstructible) && !setPropertyDirectly)) + else if (state.Current.IsProcessingObject(ClassType.Dictionary) || (state.Current.IsProcessingProperty(ClassType.Dictionary) && !setPropertyDirectly)) { - Debug.Assert(state.Current.TempDictionaryValues != null); - IDictionary dictionary = (IDictionary)state.Current.TempDictionaryValues; - string key = state.Current.KeyName; Debug.Assert(!string.IsNullOrEmpty(key)); - dictionary[key] = value; + + if (state.Current.TempDictionaryValues != null) + { + (state.Current.TempDictionaryValues)[key] = value; + } + else + { + Debug.Assert(state.Current.ReturnValue != null); + + object currentDictionary = state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue); + + if (currentDictionary is IDictionary dict) + { + Debug.Assert(!dict.IsReadOnly); + dict[key] = value; + } + else + { + state.Current.JsonPropertyInfo.AddObjectToDictionary(currentDictionary, key, value); + } + } } else { @@ -226,47 +248,65 @@ internal static void ApplyValueToEnumerable( } else { - ((IList)state.Current.ReturnValue).Add(value); + AddValueToEnumerable(ref state, state.Current.ReturnValue, value); } } else if (state.Current.IsProcessingProperty(ClassType.Enumerable)) { - Debug.Assert(state.Current.JsonPropertyInfo != null); - Debug.Assert(state.Current.ReturnValue != null); if (state.Current.TempEnumerableValues != null) { ((IList)state.Current.TempEnumerableValues).Add(value); } else { - IList list = (IList)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue); - if (list == null) + Debug.Assert(state.Current.JsonPropertyInfo != null); + Debug.Assert(state.Current.ReturnValue != null); + + JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; + + object currentEnumerable = jsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue); + if (currentEnumerable == null || + // ImmutableArray is a struct, so default value won't be null. + jsonPropertyInfo.IsImmutableArray) { - state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); + jsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); } else { - list.Add(value); + AddValueToEnumerable(ref state, currentEnumerable, value); } } } else if (state.Current.IsProcessingDictionary()) { - Debug.Assert(state.Current.ReturnValue != null); - IDictionary dictionary = (IDictionary)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue); - string key = state.Current.KeyName; Debug.Assert(!string.IsNullOrEmpty(key)); - dictionary[key] = value; - } - else if (state.Current.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; + if (state.Current.TempDictionaryValues != null) + { + ((IDictionary)state.Current.TempDictionaryValues)[key] = value; + } + else + { + Debug.Assert(state.Current.ReturnValue != null); + + object currentDictionary = state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue); + + if (currentDictionary is IDictionary genericDict) + { + Debug.Assert(!genericDict.IsReadOnly); + genericDict[key] = value; + } + else if (currentDictionary is IDictionary dict) + { + Debug.Assert(!dict.IsReadOnly); + dict[key] = value; + } + else + { + throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(currentDictionary.GetType(), parentType: null, memberInfo: null); + } + } } else { @@ -274,5 +314,25 @@ internal static void ApplyValueToEnumerable( state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void AddValueToEnumerable(ref ReadStack state, object target, TProperty value) + { + if (target is IList genericList) + { + Debug.Assert(!genericList.IsReadOnly); + genericList.Add(value); + } + else if (target is IList list) + { + Debug.Assert(!list.IsReadOnly); + list.Add(value); + } + else + { + Debug.Assert(state.Current.AddObjectToEnumerable != null); + ((Action)state.Current.AddObjectToEnumerable)(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 8eef1168b7ba..3029f027056d 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 @@ -42,11 +42,7 @@ private static void HandleStartDictionary(JsonSerializerOptions options, ref Rea JsonClassInfo classInfo = state.Current.JsonClassInfo; - if (state.Current.IsProcessingIDictionaryConstructible()) - { - state.Current.TempDictionaryValues = (IDictionary)classInfo.CreateConcreteDictionary(); - } - else + if (state.Current.IsProcessingEnumerable()) { if (classInfo.CreateObject == null) { @@ -55,43 +51,40 @@ private static void HandleStartDictionary(JsonSerializerOptions options, ref Rea } state.Current.ReturnValue = classInfo.CreateObject(); } + else if (state.Current.IsProcessingDictionary()) + { + object dictValue = ReadStackFrame.CreateDictionaryValue(ref state); + + // If value is not null, then we don't have a converter so apply the value. + if (dictValue != null) + { + state.Current.ReturnValue = dictValue; + state.Current.DetermineIfDictionaryCanBePopulated(state.Current.ReturnValue); + } + } + else + { + state.Current.ReturnValue = classInfo.CreateObject(); + } return; } state.Current.CollectionPropertyInitialized = true; - if (state.Current.IsProcessingIDictionaryConstructible()) + object value = ReadStackFrame.CreateDictionaryValue(ref state); + if (value != null) { - JsonClassInfo dictionaryClassInfo; - if (jsonPropertyInfo.DeclaredPropertyType == jsonPropertyInfo.ImplementedPropertyType) + state.Current.DetermineIfDictionaryCanBePopulated(value); + + if (state.Current.ReturnValue != null) { - dictionaryClassInfo = options.GetOrAddClass(jsonPropertyInfo.RuntimePropertyType); + state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); } else { - dictionaryClassInfo = options.GetOrAddClass(jsonPropertyInfo.DeclaredPropertyType); - } - - state.Current.TempDictionaryValues = (IDictionary)dictionaryClassInfo.CreateConcreteDictionary(); - } - else - { - // Create the dictionary. - JsonClassInfo dictionaryClassInfo = jsonPropertyInfo.RuntimeClassInfo; - IDictionary value = (IDictionary)dictionaryClassInfo.CreateObject(); - - if (value != null) - { - if (state.Current.ReturnValue != null) - { - state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); - } - else - { - // A dictionary is being returned directly, or a nested dictionary. - state.Current.SetReturnValue(value); - } + // A dictionary is being returned directly, or a nested dictionary. + state.Current.ReturnValue = value; } } } @@ -102,26 +95,28 @@ private static void HandleEndDictionary(JsonSerializerOptions options, ref ReadS if (state.Current.IsProcessingProperty(ClassType.Dictionary)) { - // Handle special case of DataExtensionProperty where we just added a dictionary element to the extension property. - // Since the JSON value is not a dictionary element (it's a normal property in JSON) a JsonTokenType.EndObject - // encountered here is from the outer object so forward to HandleEndObject(). - if (state.Current.JsonClassInfo.DataExtensionProperty == state.Current.JsonPropertyInfo) + if (state.Current.TempDictionaryValues != null) { - HandleEndObject(ref state); + JsonDictionaryConverter converter = state.Current.JsonPropertyInfo.DictionaryConverter; + state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, converter.CreateFromDictionary(ref state, state.Current.TempDictionaryValues, options)); + state.Current.EndProperty(); } else { - // We added the items to the dictionary already. - state.Current.EndProperty(); + // Handle special case of DataExtensionProperty where we just added a dictionary element to the extension property. + // Since the JSON value is not a dictionary element (it's a normal property in JSON) a JsonTokenType.EndObject + // encountered here is from the outer object so forward to HandleEndObject(). + if (state.Current.JsonClassInfo.DataExtensionProperty == state.Current.JsonPropertyInfo) + { + HandleEndObject(ref state); + } + else + { + // We added the items to the dictionary already. + state.Current.EndProperty(); + } } } - else if (state.Current.IsProcessingProperty(ClassType.IDictionaryConstructible)) - { - Debug.Assert(state.Current.TempDictionaryValues != null); - JsonDictionaryConverter converter = state.Current.JsonPropertyInfo.DictionaryConverter; - state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, converter.CreateFromDictionary(ref state, state.Current.TempDictionaryValues, options)); - state.Current.EndProperty(); - } else { object value; 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 d452e77352e7..5f59db6d1631 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,7 +8,7 @@ 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) { if (state.Current.SkipProperty) { @@ -82,7 +82,6 @@ private static void AddNullToCollection(JsonPropertyInfo jsonPropertyInfo, ref U JsonPropertyInfo elementPropertyInfo = jsonPropertyInfo.ElementClassInfo.PolicyProperty; // if elementPropertyInfo == null then this element doesn't need a converter (an object). - if (elementPropertyInfo?.CanBeNull == false) { // Allow a value type converter to return a null value representation. 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 c0b551c3b256..e80f7b80075e 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,7 +11,7 @@ public static partial class JsonSerializer { private static void HandleStartObject(JsonSerializerOptions options, ref ReadStack state) { - Debug.Assert(!state.Current.IsProcessingDictionaryOrIDictionaryConstructible()); + Debug.Assert(!state.Current.IsProcessingDictionary()); if (state.Current.IsProcessingEnumerable()) { @@ -42,9 +42,16 @@ private static void HandleStartObject(JsonSerializerOptions options, ref ReadSta } } - if (state.Current.IsProcessingIDictionaryConstructible()) + if (state.Current.IsProcessingObject(ClassType.Dictionary)) { - state.Current.TempDictionaryValues = (IDictionary)classInfo.CreateConcreteDictionary(); + object value = ReadStackFrame.CreateDictionaryValue(ref state); + + // If value is not null, then we don't have a converter so apply the value. + if (value != null) + { + state.Current.ReturnValue = value; + state.Current.DetermineIfDictionaryCanBePopulated(state.Current.ReturnValue); + } } else { @@ -55,9 +62,7 @@ private static void HandleStartObject(JsonSerializerOptions options, ref ReadSta private static void HandleEndObject(ref ReadStack state) { // Only allow dictionaries to be processed here if this is the DataExtensionProperty. - Debug.Assert( - (!state.Current.IsProcessingDictionary() || state.Current.JsonClassInfo.DataExtensionProperty == state.Current.JsonPropertyInfo) && - !state.Current.IsProcessingIDictionaryConstructible()); + Debug.Assert(!state.Current.IsProcessingDictionary() || state.Current.JsonClassInfo.DataExtensionProperty == state.Current.JsonPropertyInfo); // Check if we are trying to build the sorted cache. if (state.Current.PropertyRefCache != null) @@ -75,6 +80,7 @@ private static void HandleEndObject(ref ReadStack state) else { state.Pop(); + ApplyObjectToEnumerable(value, ref state); } } 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 1aec5514204f..890e57eda098 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 @@ -23,11 +23,11 @@ private static void HandlePropertyName( return; } - Debug.Assert(state.Current.ReturnValue != default || state.Current.TempDictionaryValues != default); - Debug.Assert(state.Current.JsonClassInfo != default); + Debug.Assert(state.Current.ReturnValue != null || state.Current.TempDictionaryValues != null); + Debug.Assert(state.Current.JsonClassInfo != null); - bool isProcessingDictObject = state.Current.IsProcessingDictionaryOrIDictionaryConstructibleObject(); - if ((isProcessingDictObject || state.Current.IsProcessingDictionaryOrIDictionaryConstructibleProperty()) && + bool isProcessingDictObject = state.Current.IsProcessingObject(ClassType.Dictionary); + if ((isProcessingDictObject || state.Current.IsProcessingProperty(ClassType.Dictionary)) && state.Current.JsonClassInfo.DataExtensionProperty != state.Current.JsonPropertyInfo) { if (isProcessingDictObject) 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 5ff0073041cb..d3f8ce83aa9a 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.IsProcessingDictionaryOrIDictionaryConstructible()) + else if (readStack.Current.IsProcessingDictionary()) { HandleStartDictionary(options, 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.IsProcessingDictionaryOrIDictionaryConstructible()) + else if (readStack.Current.IsProcessingDictionary()) { HandleEndDictionary(options, ref readStack); } @@ -100,7 +100,7 @@ private static void ReadCore( { 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)) { @@ -114,7 +114,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/JsonSerializer.Write.HandleDictionary.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs index 4ada9026807f..95100669865b 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 @@ -127,19 +127,20 @@ internal static void WriteDictionary( key = polymorphicEnumerator.Current.Key; value = (TProperty)polymorphicEnumerator.Current.Value; } - else if (current.IsIDictionaryConstructible || current.IsIDictionaryConstructibleProperty) - { - key = (string)((DictionaryEntry)current.CollectionEnumerator.Current).Key; - value = (TProperty)((DictionaryEntry)current.CollectionEnumerator.Current).Value; - } else { - // Todo: support non-generic Dictionary here (IDictionaryEnumerator) - // https://github.com/dotnet/corefx/issues/41034 - throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( - current.JsonPropertyInfo.DeclaredPropertyType, - current.JsonPropertyInfo.ParentClassType, - current.JsonPropertyInfo.PropertyInfo); + if (((DictionaryEntry)current.CollectionEnumerator.Current).Key is string keyAsString) + { + key = keyAsString; + value = (TProperty)((DictionaryEntry)current.CollectionEnumerator.Current).Value; + } + else + { + throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( + current.JsonPropertyInfo.DeclaredPropertyType, + current.JsonPropertyInfo.ParentClassType, + current.JsonPropertyInfo.PropertyInfo); + } } if (value == null) 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 0b35eb2a8423..540c86eadb01 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 @@ -125,21 +125,6 @@ private static void HandleObject( return; } - // A property that returns a type that is deserialized by passing an - // IDictionary to its constructor keeps the same stack frame. - if (jsonPropertyInfo.ClassType == ClassType.IDictionaryConstructible) - { - state.Current.IsIDictionaryConstructibleProperty = true; - - bool endOfEnumerable = HandleDictionary(jsonPropertyInfo.ElementClassInfo, options, writer, ref state); - if (endOfEnumerable) - { - state.Current.MoveToNextProperty = true; - } - - return; - } - // A property that returns an object. if (!obtainedValue) { 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 b0effbf468aa..5f25813631d5 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 @@ -36,7 +36,6 @@ private static bool Write( finishedSerializing = true; break; case ClassType.Dictionary: - case ClassType.IDictionaryConstructible: finishedSerializing = HandleDictionary(state.Current.JsonClassInfo.ElementClassInfo, options, writer, ref state); break; default: 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 b04b5e768456..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,8 +19,7 @@ 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 ConcurrentDictionary s_createRangeDelegates = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary s_createRangeDelegates = new ConcurrentDictionary(); private MemberAccessor _memberAccessorStrategy; private JsonNamingPolicy _dictionayKeyPolicy; private JsonNamingPolicy _jsonPropertyNamingPolicy; @@ -351,24 +349,6 @@ internal JsonWriterOptions GetWriterOptions() }; } - internal JsonPropertyInfo GetJsonPropertyInfoFromClassInfo(Type objectType, JsonSerializerOptions options) - { - if (!_objectJsonProperties.TryGetValue(objectType, out JsonPropertyInfo propertyInfo)) - { - propertyInfo = JsonClassInfo.CreateProperty( - objectType, - objectType, - objectType, - propertyInfo: null, - typeof(object), - converter: null, - options); - _objectJsonProperties[objectType] = propertyInfo; - } - - return propertyInfo; - } - internal bool CreateRangeDelegatesContainsKey(string key) { return s_createRangeDelegates.ContainsKey(key); @@ -384,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/MemberAccessor.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs index bb513ac62486..c3b31ad300b4 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 @@ -11,6 +11,8 @@ internal abstract class MemberAccessor { public abstract JsonClassInfo.ConstructorDelegate CreateConstructor(Type classType); + public abstract Action CreateAddDelegate(MethodInfo addMethod, object target); + 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 41c1d0bf1c56..f63a6c6355b4 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,13 +82,12 @@ private void AppendStackFrame(StringBuilder sb, in ReadStackFrame frame) } else if (frame.IsProcessingEnumerable()) { - // For enumerables add the index. IList list = frame.TempEnumerableValues; if (list == null && frame.ReturnValue != null) { + list = (IList)frame.JsonPropertyInfo?.GetValueAsObject(frame.ReturnValue); } - if (list != null) { sb.Append(@"["); 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 c7f5d72a2b4c..b5fdc82efab7 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 @@ -26,6 +26,9 @@ internal struct ReadStackFrame // Current property values. public JsonPropertyInfo JsonPropertyInfo; + // Delegate used to add elements to the current property. + public object AddObjectToEnumerable; + // Support System.Array and other types that don't implement IList. public IList TempEnumerableValues; @@ -45,28 +48,28 @@ internal struct ReadStackFrame public List PropertyRefCache; /// - /// Is the current object an Enumerable, Dictionary or IDictionaryConstructible. + /// Is the current object an Enumerable or Dictionary. /// public bool IsProcessingCollectionObject() { - return IsProcessingObject(ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible); + return IsProcessingObject(ClassType.Enumerable | ClassType.Dictionary); } /// - /// Is the current property an Enumerable, Dictionary or IDictionaryConstructible. + /// Is the current property an Enumerable or Dictionary. /// public bool IsProcessingCollectionProperty() { - return IsProcessingProperty(ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible); + return IsProcessingProperty(ClassType.Enumerable | ClassType.Dictionary); } /// - /// Is the current object or property an Enumerable, Dictionary or IDictionaryConstructible. + /// Is the current object or property an Enumerable or Dictionary. /// public bool IsProcessingCollection() { - return IsProcessingObject(ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible) || - IsProcessingProperty(ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible); + return IsProcessingObject(ClassType.Enumerable | ClassType.Dictionary) || + IsProcessingProperty(ClassType.Enumerable | ClassType.Dictionary); } /// @@ -78,40 +81,6 @@ public bool IsProcessingDictionary() IsProcessingProperty(ClassType.Dictionary); } - /// - /// Is the current object or property an IDictionaryConstructible. - /// - public bool IsProcessingIDictionaryConstructible() - { - return IsProcessingObject(ClassType.IDictionaryConstructible) - || IsProcessingProperty(ClassType.IDictionaryConstructible); - } - - /// - /// Is the current object a Dictionary or IDictionaryConstructible. - /// - public bool IsProcessingDictionaryOrIDictionaryConstructibleObject() - { - return IsProcessingObject(ClassType.Dictionary | ClassType.IDictionaryConstructible); - } - - /// - /// Is the current property a Dictionary or IDictionaryConstructible. - /// - public bool IsProcessingDictionaryOrIDictionaryConstructibleProperty() - { - return IsProcessingProperty(ClassType.Dictionary | ClassType.IDictionaryConstructible); - } - - /// - /// Is the current object or property a Dictionary or IDictionaryConstructible. - /// - public bool IsProcessingDictionaryOrIDictionaryConstructible() - { - return IsProcessingObject(ClassType.Dictionary | ClassType.IDictionaryConstructible) || - IsProcessingProperty(ClassType.Dictionary | ClassType.IDictionaryConstructible); - } - /// /// Is the current object or property an Enumerable. /// @@ -182,7 +151,7 @@ public void Initialize(Type type, JsonSerializerOptions options) public void InitializeJsonPropertyInfo() { - if (IsProcessingObject(ClassType.Value | ClassType.Enumerable | ClassType.Dictionary | ClassType.IDictionaryConstructible)) + if (IsProcessingObject(ClassType.Value | ClassType.Enumerable | ClassType.Dictionary)) { JsonPropertyInfo = JsonClassInfo.PolicyProperty; } @@ -205,6 +174,7 @@ public void EndObject() public void EndProperty() { + AddObjectToEnumerable = null; CollectionPropertyInitialized = false; JsonPropertyInfo = null; TempEnumerableValues = null; @@ -213,7 +183,7 @@ public void EndProperty() KeyName = null; } - public static object CreateEnumerableValue(ref Utf8JsonReader reader, ref ReadStack state) + public static object CreateEnumerableValue(ref ReadStack state) { JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; @@ -235,7 +205,7 @@ public static object CreateEnumerableValue(ref Utf8JsonReader reader, ref ReadSt // Clear the value if present to ensure we don't confuse tempEnumerableValues with the collection. if (!jsonPropertyInfo.IsPropertyPolicy && - !state.Current.JsonPropertyInfo.RuntimePropertyType.FullName.StartsWith(DefaultImmutableEnumerableConverter.ImmutableArrayGenericTypeName)) + !state.Current.JsonPropertyInfo.IsImmutableArray) { jsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, null); } @@ -243,35 +213,62 @@ public static object CreateEnumerableValue(ref Utf8JsonReader reader, ref ReadSt return null; } - Type propertyType = jsonPropertyInfo.RuntimePropertyType; - if (typeof(IList).IsAssignableFrom(propertyType)) + JsonClassInfo runtimeClassInfo = jsonPropertyInfo.RuntimeClassInfo; + if (runtimeClassInfo.CreateObject != null) + { + return runtimeClassInfo.CreateObject(); + } + else + { + // Could not create an instance to be returned. For derived types, this means there is no parameterless ctor. + throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( + jsonPropertyInfo.DeclaredPropertyType, + jsonPropertyInfo.ParentClassType, + jsonPropertyInfo.PropertyInfo); + } + } + + public static object CreateDictionaryValue(ref ReadStack state) + { + JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; + + // If the property has a DictionaryConverter, then we use tempDictionaryValues. + if (jsonPropertyInfo.DictionaryConverter != null) { - // If IList, add the members as we create them. - JsonClassInfo collectionClassInfo; - - if (jsonPropertyInfo.DeclaredPropertyType == jsonPropertyInfo.ImplementedPropertyType) + IDictionary converterDictionary; + JsonClassInfo elementClassInfo = jsonPropertyInfo.ElementClassInfo; + if (elementClassInfo.ClassType == ClassType.Value) { - collectionClassInfo = jsonPropertyInfo.RuntimeClassInfo; + converterDictionary = elementClassInfo.PolicyProperty.CreateConverterDictionary(); } else { - collectionClassInfo = jsonPropertyInfo.DeclaredTypeClassInfo; + converterDictionary = new Dictionary(); } - if (collectionClassInfo.CreateObject() is IList collection) - { - return collection; - } - else + state.Current.TempDictionaryValues = converterDictionary; + + // Clear the value if present to ensure we don't confuse tempEnumerableValues with the collection. + if (!jsonPropertyInfo.IsPropertyPolicy) { - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(jsonPropertyInfo.DeclaredPropertyType); - return null; + jsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, null); } + + return null; + } + + JsonClassInfo runtimeClassInfo = jsonPropertyInfo.RuntimeClassInfo; + if (runtimeClassInfo.CreateObject != null) + { + return runtimeClassInfo.CreateObject(); } else { - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(propertyType); - return null; + // Could not create an instance to be returned. For derived types, this means there is no parameterless ctor. + throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( + jsonPropertyInfo.DeclaredPropertyType, + jsonPropertyInfo.ParentClassType, + jsonPropertyInfo.PropertyInfo); } } @@ -304,10 +301,50 @@ public static IEnumerable GetEnumerableValue(in ReadStackFrame current) return current.TempEnumerableValues; } - public void SetReturnValue(object value) + public void DetermineEnumerablePopulationStrategy(object targetEnumerable) + { + if (JsonPropertyInfo.RuntimeClassInfo.AddItemToObject != null) + { + if (!JsonPropertyInfo.TryCreateEnumerableAddMethod(targetEnumerable, out object addMethodDelegate)) + { + // No "add" method for this collection, hence, not supported for deserialization. + throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( + JsonPropertyInfo.DeclaredPropertyType, + JsonPropertyInfo.ParentClassType, + JsonPropertyInfo.PropertyInfo); + } + + AddObjectToEnumerable = addMethodDelegate; + } + else if (targetEnumerable is IList targetList) + { + if (targetList.IsReadOnly) + { + throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( + JsonPropertyInfo.DeclaredPropertyType, + JsonPropertyInfo.ParentClassType, + JsonPropertyInfo.PropertyInfo); + } + } + // If there's no add method, and we can't cast to IList, this collection is not supported for deserialization. + else + { + throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( + JsonPropertyInfo.DeclaredPropertyType, + JsonPropertyInfo.ParentClassType, + JsonPropertyInfo.PropertyInfo); + } + } + + public void DetermineIfDictionaryCanBePopulated(object targetDictionary) { - Debug.Assert(ReturnValue == null); - ReturnValue = value; + if (!JsonPropertyInfo.CanPopulateDictionary(targetDictionary)) + { + throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection( + JsonPropertyInfo.DeclaredPropertyType, + JsonPropertyInfo.ParentClassType, + JsonPropertyInfo.PropertyInfo); + } } public bool SkipProperty => Drain || 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 27e9812d5f1a..baa6bc105f07 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 @@ -54,6 +54,11 @@ public override JsonClassInfo.ConstructorDelegate CreateConstructor(Type type) return (JsonClassInfo.ConstructorDelegate)dynamicMethod.CreateDelegate(typeof(JsonClassInfo.ConstructorDelegate)); } + public override Action CreateAddDelegate(MethodInfo addMethod, object target) + { + Debug.Assert(addMethod != null && target != null); + return (Action)addMethod.CreateDelegate(typeof(Action), target); + } public override ImmutableCollectionCreator ImmutableCollectionCreateRange(Type constructingType, Type collectionType, Type elementType) { @@ -97,6 +102,14 @@ public override ImmutableCollectionCreator ImmutableCollectionCreateRange(Type c public override ImmutableCollectionCreator ImmutableDictionaryCreateRange(Type constructingType, Type collectionType, Type elementType) { + Debug.Assert(collectionType.IsGenericType); + + // Only string keys are allowed. + if (collectionType.GetGenericArguments()[0] != typeof(string)) + { + throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(collectionType, parentType: null, memberInfo: null); + } + MethodInfo createRange = ImmutableDictionaryCreateRangeMethod(constructingType, elementType); if (createRange == null) 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 9e73e4905706..58abb13c167b 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 @@ -42,6 +42,12 @@ public override JsonClassInfo.ConstructorDelegate CreateConstructor(Type type) return () => Activator.CreateInstance(type); } + public override Action CreateAddDelegate(MethodInfo addMethod, object target) + { + Debug.Assert(addMethod != null && target != null); + return (Action)addMethod.CreateDelegate(typeof(Action), target); + } + public override ImmutableCollectionCreator ImmutableCollectionCreateRange(Type constructingType, Type collectionType, Type elementType) { MethodInfo createRange = ImmutableCollectionCreateRangeMethod(constructingType, elementType); @@ -67,6 +73,14 @@ public override ImmutableCollectionCreator ImmutableCollectionCreateRange(Type c public override ImmutableCollectionCreator ImmutableDictionaryCreateRange(Type constructingType, Type collectionType, Type elementType) { + Debug.Assert(collectionType.IsGenericType); + + // Only string keys are allowed. + if (collectionType.GetGenericArguments()[0] != typeof(string)) + { + throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(collectionType, parentType: null, memberInfo: null); + } + MethodInfo createRange = ImmutableDictionaryCreateRangeMethod(constructingType, elementType); if (createRange == null) 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 d734f4b01c56..a9157b23aa0a 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 @@ -52,13 +52,6 @@ public void Push(JsonClassInfo nextClassInfo, object nextValue) Current.PopStackOnEndCollection = true; Current.JsonPropertyInfo = Current.JsonClassInfo.PolicyProperty; } - else if (classType == ClassType.IDictionaryConstructible) - { - Current.PopStackOnEndCollection = true; - Current.JsonPropertyInfo = Current.JsonClassInfo.PolicyProperty; - - Current.IsIDictionaryConstructible = true; - } else { Debug.Assert(nextClassInfo.ClassType == ClassType.Object || nextClassInfo.ClassType == ClassType.Unknown); 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 740702ff77b5..afa42b75a9fa 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 @@ -22,8 +22,6 @@ internal struct WriteStackFrame public IEnumerator CollectionEnumerator; // Note all bools are kept together for packing: public bool PopStackOnEndCollection; - public bool IsIDictionaryConstructible; - public bool IsIDictionaryConstructibleProperty; // The current object. public bool PopStackOnEndObject; @@ -38,15 +36,10 @@ internal struct WriteStackFrame public void Initialize(Type type, JsonSerializerOptions options) { JsonClassInfo = options.GetOrAddClass(type); - if (JsonClassInfo.ClassType == ClassType.Value || JsonClassInfo.ClassType == ClassType.Enumerable || JsonClassInfo.ClassType == ClassType.Dictionary) + if ((JsonClassInfo.ClassType & (ClassType.Value | ClassType.Enumerable | ClassType.Dictionary)) != 0) { JsonPropertyInfo = JsonClassInfo.PolicyProperty; } - else if (JsonClassInfo.ClassType == ClassType.IDictionaryConstructible) - { - JsonPropertyInfo = JsonClassInfo.PolicyProperty; - IsIDictionaryConstructible = true; - } } public void WriteObjectOrArrayStart(ClassType classType, Utf8JsonWriter writer, JsonSerializerOptions options, bool writeNull = false) @@ -65,7 +58,7 @@ 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; @@ -84,9 +77,7 @@ private void WriteObjectOrArrayStart(ClassType classType, JsonEncodedText proper { writer.WriteNull(propertyName); } - else if (classType == ClassType.Object || - classType == ClassType.Dictionary || - classType == ClassType.IDictionaryConstructible) + else if ((classType & (ClassType.Object | ClassType.Dictionary)) != 0) { writer.WriteStartObject(propertyName); StartObjectWritten = true; @@ -103,7 +94,6 @@ public void Reset() CurrentValue = null; CollectionEnumerator = null; ExtensionDataStatus = ExtensionDataWriteStatus.NotStarted; - IsIDictionaryConstructible = false; JsonClassInfo = null; PropertyEnumeratorIndex = 0; PopStackOnEndCollection = false; @@ -116,7 +106,6 @@ public void Reset() [MethodImpl(MethodImplOptions.AggressiveInlining)] public void EndProperty() { - IsIDictionaryConstructibleProperty = false; JsonPropertyInfo = null; KeyName = null; MoveToNextProperty = false; diff --git a/src/System.Text.Json/tests/NewtonsoftTests/DateTimeConverterTests.cs b/src/System.Text.Json/tests/NewtonsoftTests/DateTimeConverterTests.cs index b156bf1ef568..ff193ff25ef4 100644 --- a/src/System.Text.Json/tests/NewtonsoftTests/DateTimeConverterTests.cs +++ b/src/System.Text.Json/tests/NewtonsoftTests/DateTimeConverterTests.cs @@ -27,7 +27,6 @@ using System.Globalization; using Xunit; -using System.Text.Json.Serialization; namespace System.Text.Json.Tests { diff --git a/src/System.Text.Json/tests/Serialization/CustomConverterTests.DerivedTypes.cs b/src/System.Text.Json/tests/Serialization/CustomConverterTests.DerivedTypes.cs index 680d862543f6..37b2765cb2b2 100644 --- a/src/System.Text.Json/tests/Serialization/CustomConverterTests.DerivedTypes.cs +++ b/src/System.Text.Json/tests/Serialization/CustomConverterTests.DerivedTypes.cs @@ -102,7 +102,7 @@ public static void CustomUnsupportedIEnumerableConverter() UnsupportedDerivedTypesWrapper_IEnumerable wrapper = new UnsupportedDerivedTypesWrapper_IEnumerable { - IEnumerableWrapper = new StringIEnumerableWrapper() { "1", "2", "3" }, + IEnumerableWrapper = new StringIEnumerableWrapper(new List { "1", "2", "3" }), }; // Without converter, we throw on deserialize. diff --git a/src/System.Text.Json/tests/Serialization/DictionaryTests.cs b/src/System.Text.Json/tests/Serialization/DictionaryTests.cs index b748f63cc14e..6aad4d50bfce 100644 --- a/src/System.Text.Json/tests/Serialization/DictionaryTests.cs +++ b/src/System.Text.Json/tests/Serialization/DictionaryTests.cs @@ -888,7 +888,7 @@ public static void HashtableFail() { IDictionary ht = new Hashtable(); ht.Add("Key", "Value"); - Assert.Throws(() => JsonSerializer.Serialize(ht)); + Assert.Equal(@"{""Key"":""Value""}", JsonSerializer.Serialize(ht)); } } diff --git a/src/System.Text.Json/tests/Serialization/ExtensionDataTests.cs b/src/System.Text.Json/tests/Serialization/ExtensionDataTests.cs index 89035111f5c0..8d6afbe9cd1a 100644 --- a/src/System.Text.Json/tests/Serialization/ExtensionDataTests.cs +++ b/src/System.Text.Json/tests/Serialization/ExtensionDataTests.cs @@ -491,7 +491,7 @@ public static void ExtensionProperty_InvalidDictionary() Assert.Throws(() => JsonSerializer.Serialize(obj1)); ClassWithInvalidExtensionPropertyObjectString obj2 = new ClassWithInvalidExtensionPropertyObjectString(); - Assert.Throws(() => JsonSerializer.Serialize(obj2)); + Assert.Throws(() => JsonSerializer.Serialize(obj2)); } private class ClassWithExtensionPropertyAlreadyInstantiated diff --git a/src/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs b/src/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs index b772be08ad97..438291b5c1ae 100644 --- a/src/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs +++ b/src/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs @@ -154,15 +154,31 @@ public static void JsonIgnoreAttribute_UnsupportedCollection() } }"; - // Unsupported collections will throw by default. + // Unsupported collections will throw on deserialize by default. Assert.Throws(() => JsonSerializer.Deserialize(json)); + // Using new options instance to prevent using previously cached metadata. JsonSerializerOptions options = new JsonSerializerOptions(); - Assert.Throws(() => JsonSerializer.Serialize(new ClassWithUnsupportedDictionary(), options)); + string serialized = JsonSerializer.Serialize(new ClassWithUnsupportedDictionary(), options); + + // Object keys are fine on serialization if the keys are strings. + Assert.Contains(@"""MyConcurrentDict"":null", serialized); + Assert.Contains(@"""MyIDict"":null", serialized); + Assert.Contains(@"""MyDict"":null", serialized); + + // Unsupported collections will throw on deserialize by default. options = new JsonSerializerOptions(); Assert.Throws(() => JsonSerializer.Deserialize(wrapperJson, options)); + options = new JsonSerializerOptions(); - Assert.Throws(() => JsonSerializer.Serialize(new WrapperForClassWithUnsupportedDictionary(), options)); + serialized = JsonSerializer.Serialize(new WrapperForClassWithUnsupportedDictionary(), options); + + // Object keys are fine on serialization if the keys are strings. + Assert.Contains(@"{""MyClass"":{", serialized); + Assert.Contains(@"""MyConcurrentDict"":null", serialized); + Assert.Contains(@"""MyIDict"":null", serialized); + Assert.Contains(@"""MyDict"":null", serialized); + Assert.Contains("}}", serialized); // When ignored, we can serialize and deserialize without exceptions. options = new JsonSerializerOptions(); diff --git a/src/System.Text.Json/tests/Serialization/TestClasses.GenericCollections.cs b/src/System.Text.Json/tests/Serialization/TestClasses.GenericCollections.cs index cb7b5294195e..d6ffc6ccb848 100644 --- a/src/System.Text.Json/tests/Serialization/TestClasses.GenericCollections.cs +++ b/src/System.Text.Json/tests/Serialization/TestClasses.GenericCollections.cs @@ -93,7 +93,7 @@ public class SimpleTestClassWithStringIEnumerableWrapper // Call only when testing serialization. public void Initialize() { - MyStringIEnumerableWrapper = new StringIEnumerableWrapper() { "Hello" }; + MyStringIEnumerableWrapper = new StringIEnumerableWrapper(new List{ "Hello" }); } } @@ -111,7 +111,7 @@ public class SimpleTestClassWithStringIReadOnlyCollectionWrapper // Call only when testing serialization. public void Initialize() { - MyStringIReadOnlyCollectionWrapper = new StringIReadOnlyCollectionWrapper() { "Hello" }; + MyStringIReadOnlyCollectionWrapper = new StringIReadOnlyCollectionWrapper(new List { "Hello" }); } } @@ -129,7 +129,7 @@ public class SimpleTestClassWithStringIReadOnlyListWrapper // Call only when testing serialization. public void Initialize() { - MyStringIReadOnlyListWrapper = new StringIReadOnlyListWrapper() { "Hello" }; + MyStringIReadOnlyListWrapper = new StringIReadOnlyListWrapper(new List { "Hello" }); } } @@ -156,10 +156,11 @@ public class StringIEnumerableWrapper : IEnumerable { private readonly List _list = new List(); - // For populating test data only. We can't rely on this method for real input. - public void Add(string item) + public StringIEnumerableWrapper() { } + + public StringIEnumerableWrapper(List items) { - _list.Add(item); + _list = items; } public IEnumerator GetEnumerator() @@ -177,9 +178,11 @@ public class GenericIEnumerableWrapper : IEnumerable { private readonly List _list = new List(); - public void Add(T item) + public GenericIEnumerableWrapper() { } + + public GenericIEnumerableWrapper(List items) { - _list.Add(item); + _list = items; } public IEnumerator GetEnumerator() @@ -201,7 +204,7 @@ public class StringICollectionWrapper : ICollection public virtual bool IsReadOnly => ((ICollection)_list).IsReadOnly; - public void Add(string item) + public virtual void Add(string item) { _list.Add(item); } @@ -252,7 +255,7 @@ public class StringIListWrapper : IList public virtual bool IsReadOnly => ((IList)_list).IsReadOnly; - public void Add(string item) + public virtual void Add(string item) { _list.Add(item); } @@ -417,10 +420,11 @@ public class StringIReadOnlyCollectionWrapper : IReadOnlyCollection { private readonly List _list = new List(); - // For populating test data only. We cannot assume actual input will have this method. - public void Add(string item) + public StringIReadOnlyCollectionWrapper() { } + + public StringIReadOnlyCollectionWrapper(List list) { - _list.Add(item); + _list = list; } public int Count => _list.Count; @@ -440,9 +444,11 @@ public class GenericIReadOnlyCollectionWrapper : IReadOnlyCollection { private readonly List _list = new List(); - public void Add(T item) + public GenericIReadOnlyCollectionWrapper() { } + + public GenericIReadOnlyCollectionWrapper(List list) { - _list.Add(item); + _list = list; } public int Count => _list.Count; @@ -462,10 +468,11 @@ public class StringIReadOnlyListWrapper : IReadOnlyList { private readonly List _list = new List(); - // For populating test data only. We cannot assume actual input will have this method. - public void Add(string item) + public StringIReadOnlyListWrapper() { } + + public StringIReadOnlyListWrapper(List list) { - _list.Add(item); + _list = list; } public string this[int index] => _list[index]; @@ -487,9 +494,11 @@ public class GenericIReadOnlyListWrapper : IReadOnlyList { private readonly List _list = new List(); - public void Add(T item) + public GenericIReadOnlyListWrapper() { } + + public GenericIReadOnlyListWrapper(List list) { - _list.Add(item); + _list = list; } public T this[int index] => _list[index]; @@ -726,7 +735,7 @@ public StringToStringIDictionaryWrapper(Dictionary dictionary) public virtual bool IsReadOnly => ((IDictionary)_dictionary).IsReadOnly; - public void Add(string key, string value) + public virtual void Add(string key, string value) { ((IDictionary)_dictionary).Add(key, value); } @@ -976,6 +985,14 @@ IEnumerator IEnumerable.GetEnumerator() public class StringListWrapper : List { } + class MyMyList : GenericListWrapper + { + } + + class MyListString : GenericListWrapper + { + } + public class GenericListWrapper : List { } public class StringStackWrapper : Stack diff --git a/src/System.Text.Json/tests/Serialization/TestClasses.NonGenericCollections.cs b/src/System.Text.Json/tests/Serialization/TestClasses.NonGenericCollections.cs index f549043674d1..7952ea6ff2c0 100644 --- a/src/System.Text.Json/tests/Serialization/TestClasses.NonGenericCollections.cs +++ b/src/System.Text.Json/tests/Serialization/TestClasses.NonGenericCollections.cs @@ -15,6 +15,8 @@ public class SimpleTestClassWithNonGenericCollectionWrappers : ITestClass public HashtableWrapper MyHashtableWrapper { get; set; } public ArrayListWrapper MyArrayListWrapper { get; set; } public SortedListWrapper MySortedListWrapper { get; set; } + public StackWrapper MyStackWrapper { get; set; } + public QueueWrapper MyQueueWrapper { get; set; } public static readonly string s_json = @"{" + @@ -22,7 +24,9 @@ public class SimpleTestClassWithNonGenericCollectionWrappers : ITestClass @"""MyIDictionaryWrapper"" : {""key"" : ""value""}," + @"""MyHashtableWrapper"" : {""key"" : ""value""}," + @"""MyArrayListWrapper"" : [""Hello""]," + - @"""MySortedListWrapper"" : {""key"" : ""value""}" + + @"""MySortedListWrapper"" : {""key"" : ""value""}," + + @"""MyStackWrapper"" : [""Hello""]," + + @"""MyQueueWrapper"" : [""Hello""]" + @"}"; public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); @@ -34,6 +38,11 @@ public void Initialize() MyHashtableWrapper = new HashtableWrapper(new List> { new KeyValuePair("key", "value" ) }); MyArrayListWrapper = new ArrayListWrapper() { "Hello" }; MySortedListWrapper = new SortedListWrapper() { { "key", "value" } }; + MyStackWrapper = new StackWrapper(); + MyQueueWrapper = new QueueWrapper(); + + MyStackWrapper.Push("Hello"); + MyQueueWrapper.Enqueue("Hello"); } public void Verify() @@ -43,6 +52,8 @@ public void Verify() Assert.Equal("value", ((JsonElement)MyHashtableWrapper["key"]).GetString()); Assert.Equal("Hello", ((JsonElement)MyArrayListWrapper[0]).GetString()); Assert.Equal("value", ((JsonElement)MySortedListWrapper["key"]).GetString()); + Assert.Equal("Hello", ((JsonElement)MyStackWrapper.Peek()).GetString()); + Assert.Equal("Hello", ((JsonElement)MyQueueWrapper.Peek()).GetString()); } } 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..d07037b7ee60 100644 --- a/src/System.Text.Json/tests/Serialization/Value.ReadTests.GenericCollections.cs +++ b/src/System.Text.Json/tests/Serialization/Value.ReadTests.GenericCollections.cs @@ -1154,5 +1154,17 @@ public static void ReadReadOnlyCollections_Throws() Assert.Throws(() => JsonSerializer.Deserialize(@"[""1"", ""2""]")); Assert.Throws(() => JsonSerializer.Deserialize(@"{""Key"":""key"",""Value"":""value""}")); } + + [Fact] + public static void Read_HigherOrderCollectionInheritance_Works() + { + const string json = "[\"test\"]"; + Assert.Equal("test", JsonSerializer.Deserialize(json)[0]); + Assert.Equal("test", JsonSerializer.Deserialize>(json).First()); + Assert.Equal("test", JsonSerializer.Deserialize(json).First()); + Assert.Equal("test", JsonSerializer.Deserialize>(json).First()); + Assert.Equal("test", JsonSerializer.Deserialize>(json).First()); + Assert.Equal("test", JsonSerializer.Deserialize(json).First()); + } } } diff --git a/src/System.Text.Json/tests/Serialization/Value.ReadTests.NonGenericCollections.cs b/src/System.Text.Json/tests/Serialization/Value.ReadTests.NonGenericCollections.cs index 8cd21e1ad65a..a3a3ec9bdf1f 100644 --- a/src/System.Text.Json/tests/Serialization/Value.ReadTests.NonGenericCollections.cs +++ b/src/System.Text.Json/tests/Serialization/Value.ReadTests.NonGenericCollections.cs @@ -304,8 +304,13 @@ public static void ReadPrimitiveStack() } Assert.Equal(0, count); - // TODO: use reflection to support types deriving from Stack. - Assert.Throws(() => JsonSerializer.Deserialize(@"[1,2]")); + StackWrapper wrapper = JsonSerializer.Deserialize(@"[1,2]"); + expected = 2; + + foreach (JsonElement i in wrapper) + { + Assert.Equal(expected--, i.GetInt32()); + } } [Fact] @@ -374,8 +379,13 @@ public static void ReadPrimitiveQueue() } Assert.Equal(0, count); - // TODO: use reflection to support types deriving from Queue. - Assert.Throws(() => JsonSerializer.Deserialize(@"[1,2]")); + QueueWrapper wrapper = JsonSerializer.Deserialize(@"[1,2]"); + expected = 1; + + foreach (JsonElement i in wrapper) + { + Assert.Equal(expected++, i.GetInt32()); + } } [Fact] @@ -453,8 +463,6 @@ public static void ReadSimpleTestClass_NonGenericWrappers_NoAddMethod_Throws() { Assert.Throws(() => JsonSerializer.Deserialize(SimpleTestClassWithIEnumerableWrapper.s_json)); Assert.Throws(() => JsonSerializer.Deserialize(SimpleTestClassWithICollectionWrapper.s_json)); - Assert.Throws(() => JsonSerializer.Deserialize(SimpleTestClassWithStackWrapper.s_json)); - Assert.Throws(() => JsonSerializer.Deserialize(SimpleTestClassWithQueueWrapper.s_json)); } } } diff --git a/src/System.Text.Json/tests/Serialization/Value.WriteTests.GenericCollections.cs b/src/System.Text.Json/tests/Serialization/Value.WriteTests.GenericCollections.cs index ee9e965bb70e..4dfb02b42b17 100644 --- a/src/System.Text.Json/tests/Serialization/Value.WriteTests.GenericCollections.cs +++ b/src/System.Text.Json/tests/Serialization/Value.WriteTests.GenericCollections.cs @@ -77,11 +77,11 @@ public static void WriteGenericIEnumerableOfGenericIEnumerable() string json = JsonSerializer.Serialize(input); Assert.Equal("[[1,2],[3,4]]", json); - GenericIEnumerableWrapper input2 = new GenericIEnumerableWrapper + GenericIEnumerableWrapper input2 = new GenericIEnumerableWrapper(new List { - new StringIEnumerableWrapper() { "1", "2" }, - new StringIEnumerableWrapper() { "3", "4" } - }; + new StringIEnumerableWrapper(new List { "1", "2" }), + new StringIEnumerableWrapper(new List { "3", "4" }), + }); json = JsonSerializer.Serialize(input2); Assert.Equal(@"[[""1"",""2""],[""3"",""4""]]", json); @@ -242,11 +242,11 @@ public static void WriteGenericIReadOnlyCollectionOfGenericIReadOnlyCollection() string json = JsonSerializer.Serialize(input); Assert.Equal("[[1,2],[3,4]]", json); - GenericIReadOnlyCollectionWrapper input2 = new GenericIReadOnlyCollectionWrapper + GenericIReadOnlyCollectionWrapper input2 = new GenericIReadOnlyCollectionWrapper(new List { - new StringIReadOnlyCollectionWrapper() { "1", "2" }, - new StringIReadOnlyCollectionWrapper() { "3", "4" } - }; + new StringIReadOnlyCollectionWrapper(new List { "1", "2" }), + new StringIReadOnlyCollectionWrapper(new List { "3", "4" }) + }); json = JsonSerializer.Serialize(input2); Assert.Equal(@"[[""1"",""2""],[""3"",""4""]]", json); @@ -297,11 +297,11 @@ public static void WriteIReadOnlyListTOfIReadOnlyListT() string json = JsonSerializer.Serialize(input); Assert.Equal("[[1,2],[3,4]]", json); - GenericIReadOnlyListWrapper input2 = new GenericIReadOnlyListWrapper + GenericIReadOnlyListWrapper input2 = new GenericIReadOnlyListWrapper(new List { - new StringIReadOnlyListWrapper() { "1", "2" }, - new StringIReadOnlyListWrapper() { "3", "4" } - }; + new StringIReadOnlyListWrapper(new List { "1", "2" }), + new StringIReadOnlyListWrapper(new List { "3", "4" }) + }); json = JsonSerializer.Serialize(input2); Assert.Equal(@"[[""1"",""2""],[""3"",""4""]]", json);