From 8de859a12ce2a5020e984ffef5f80756b216e528 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Sat, 15 Jun 2019 19:49:03 -0400 Subject: [PATCH] Add support for more collections (dotnet/corefx#38319) * Add support for ISet * Add support for IEnumerable, IList, and ICollection (non-generic) * Add support for non-generic IDictionary * Address review feedback * Remove unused exception resource and remove ISet> (de)serialization limitation * Add support for non-generic collections that have a constructor that takes an IEnumerable or IDictionary * Condense branches for types we need to set runtime types for * Add support for non-generic collections that have a constructor that takes an IList * Add support for Hashtable and SortedList * Verify sorted dictionary works * Add support for KeyValuePair * Create concrete instance of types Foo where possible * Use generic constructor for KeyValuePair * Fix KeyValuePair creation in tests Commit migrated from https://github.com/dotnet/corefx/commit/4aece7a121a164e68954d79a0ec2569bd71cf125 --- .../System.Text.Json/src/Resources/Strings.resx | 3 - .../System.Text.Json/src/System.Text.Json.csproj | 8 +- .../System/Text/Json/Serialization/ClassType.cs | 23 +- .../Converters/DefaultEnumerableConverter.cs | 93 ----- ...Converter.cs => DefaultICollectionConverter.cs} | 5 +- .../Converters/DefaultIDictionaryConverter.cs | 20 ++ .../DefaultImmutableDictionaryConverter.cs | 69 ++++ ...r.cs => DefaultImmutableEnumerableConverter.cs} | 126 ++----- .../Serialization/DefaultIDictionaryConverter.cs | 18 + .../Serialization/JsonClassInfo.AddProperty.cs | 21 +- .../Text/Json/Serialization/JsonClassInfo.cs | 207 +++++++++-- .../Json/Serialization/JsonDictionaryConverter.cs | 13 + .../Json/Serialization/JsonEnumerableConverter.cs | 1 - .../Text/Json/Serialization/JsonPropertyInfo.cs | 73 ++-- .../Json/Serialization/JsonPropertyInfoCommon.cs | 125 ++++++- .../Serialization/JsonPropertyInfoNotNullable.cs | 36 +- .../JsonSerializer.Read.HandleArray.cs | 11 +- .../JsonSerializer.Read.HandleDictionary.cs | 63 +++- .../JsonSerializer.Read.HandleNull.cs | 4 +- .../JsonSerializer.Read.HandleObject.cs | 6 +- .../JsonSerializer.Read.HandlePropertyName.cs | 10 +- .../Text/Json/Serialization/JsonSerializer.Read.cs | 4 +- .../JsonSerializer.Write.HandleDictionary.cs | 2 +- .../JsonSerializer.Write.HandleEnumerable.cs | 6 + .../JsonSerializer.Write.HandleObject.cs | 12 +- .../Json/Serialization/JsonSerializer.Write.cs | 3 +- .../Json/Serialization/JsonSerializerOptions.cs | 26 ++ .../Text/Json/Serialization/ReadStackFrame.cs | 54 ++- .../Serialization/ReflectionEmitMaterializer.cs | 5 +- .../Json/Serialization/ReflectionMaterializer.cs | 5 +- .../System/Text/Json/Serialization/WriteStack.cs | 6 +- .../Text/Json/Serialization/WriteStackFrame.cs | 27 +- .../System/Text/Json/ThrowHelper.Serialization.cs | 7 + .../tests/Serialization/Array.ReadTests.cs | 54 +++ .../tests/Serialization/DictionaryTests.cs | 183 ++++++++-- .../tests/Serialization/Object.ReadTests.cs | 1 + .../tests/Serialization/PolymorphicTests.cs | 51 ++- .../tests/Serialization/TestClasses.Polymorphic.cs | 8 + .../Serialization/TestClasses.SimpleTestClass.cs | 127 ++++++- .../TestClasses.SimpleTestClassWithObject.cs | 69 +++- .../Serialization/TestClasses.SimpleTestStruct.cs | 6 +- .../tests/Serialization/TestClasses.cs | 356 +++++++++++++++++- .../tests/Serialization/TestData.cs | 16 + .../Value.ReadTests.GenericCollections.cs | 158 ++++++++ .../Value.ReadTests.NonGenericCollections.cs | 398 +++++++++++++++++++++ .../Value.WriteTests.GenericCollections.cs | 184 +++++++++- .../Value.WriteTests.NonGenericCollections.cs | 263 ++++++++++++++ .../tests/System.Text.Json.Tests.csproj | 2 + 48 files changed, 2546 insertions(+), 422 deletions(-) delete mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumerableConverter.cs rename src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/{DefaultIEnumerableConstructibleConverter.cs => DefaultICollectionConverter.cs} (77%) create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs rename src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/{DefaultImmutableConverter.cs => DefaultImmutableEnumerableConverter.cs} (53%) create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/DefaultIDictionaryConverter.cs create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs create mode 100644 src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.NonGenericCollections.cs create mode 100644 src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.NonGenericCollections.cs diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index 9e06826..fab63f0 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -344,9 +344,6 @@ An item with the same property name '{0}' has already been added. - - Deserialization of type {0} is not supported. - The data extension property '{0}.{1}' does not match the required signature of IDictionary<string, JsonElement> or IDictionary<string, object>. diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index 6010ed1..44c9fde 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -52,9 +52,10 @@ - - - + + + + @@ -80,6 +81,7 @@ + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs index 334e86b..1554688 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs @@ -9,11 +9,22 @@ namespace System.Text.Json /// internal enum ClassType { - Unknown = 0, // typeof(object) - Object = 1, // POCO or rich data type - Value = 2, // Data type with single value - Enumerable = 3, // IEnumerable - Dictionary = 4, // IDictionary - ImmutableDictionary = 5, // Immutable Dictionary + // typeof(object) + Unknown = 0, + // POCO or rich data type + Object = 1, + // Data type with single value + Value = 2, + // IEnumerable + Enumerable = 3, + // IDictionary + Dictionary = 4, + // Is deserialized by passing a IDictionary to its constructor + // i.e. immutable dictionaries, Hashtable, SortedList, + IDictionaryConstructible = 5, + // KeyValuePair + // TODO: Utilize converter mechanism to handle (de)serialization of KeyValuePair + // when it goes through: https://github.com/dotnet/corefx/issues/36639. + KeyValuePair = 6, } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumerableConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumerableConverter.cs deleted file mode 100644 index 3e8c81f..0000000 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumerableConverter.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections; -using System.Collections.Generic; -using System.Text.Json.Serialization.Policies; - -namespace System.Text.Json.Serialization.Converters -{ - internal class JsonEnumerableT : ICollection, IEnumerable, IList, IReadOnlyCollection, IReadOnlyList - { - List _list; - - public JsonEnumerableT(IList sourceList) - { - // TODO: Change sourceList from IList to List so we can do a direct assignment here. - _list = new List(); - - foreach (object item in sourceList) - { - _list.Add((T)item); - } - } - - public T this[int index] { get => (T)_list[index]; set => _list[index] = value; } - - public int Count => _list.Count; - - public bool IsReadOnly => false; - - public void Add(T item) - { - _list.Add(item); - } - - public void Clear() - { - _list.Clear(); - } - - public bool Contains(T item) - { - return _list.Contains(item); - } - - public void CopyTo(T[] array, int arrayIndex) - { - _list.CopyTo(array, arrayIndex); - } - - public IEnumerator GetEnumerator() - { - return _list.GetEnumerator(); - } - - public int IndexOf(T item) - { - return _list.IndexOf(item); - } - - public void Insert(int index, T item) - { - _list.Insert(index, item); - } - - public bool Remove(T item) - { - return _list.Remove(item); - } - - public void RemoveAt(int index) - { - _list.RemoveAt(index); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } - - internal sealed class DefaultEnumerableConverter : JsonEnumerableConverter - { - public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options) - { - Type elementType = state.Current.GetElementType(); - - Type t = typeof(JsonEnumerableT<>).MakeGenericType(elementType); - return (IEnumerable)Activator.CreateInstance(t, sourceList); - } - } -} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIEnumerableConstructibleConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs similarity index 77% rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIEnumerableConstructibleConverter.cs rename to src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs index 31066a1..6574501 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIEnumerableConstructibleConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultICollectionConverter.cs @@ -3,19 +3,18 @@ // See the LICENSE file in the project root for more information. using System.Collections; -using System.Collections.Concurrent; using System.Text.Json.Serialization.Policies; namespace System.Text.Json.Serialization.Converters { - internal sealed class DefaultIEnumerableConstructibleConverter : JsonEnumerableConverter + internal sealed class DefaultICollectionConverter : JsonEnumerableConverter { public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options) { Type enumerableType = state.Current.JsonPropertyInfo.RuntimePropertyType; JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo; JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementClassInfo, options); - return propertyInfo.CreateIEnumerableConstructibleType(enumerableType, sourceList); + return propertyInfo.CreateIEnumerableInstance(enumerableType, sourceList, state.JsonPath, options); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs new file mode 100644 index 0000000..351a019 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultIDictionaryConverter.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Text.Json.Serialization.Policies; + +namespace System.Text.Json.Serialization.Converters +{ + internal sealed class DefaultIDictionaryConverter : JsonDictionaryConverter + { + public override IDictionary CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options) + { + Type dictionaryType = state.Current.JsonPropertyInfo.RuntimePropertyType; + JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo; + JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementClassInfo, options); + return propertyInfo.CreateIDictionaryInstance(dictionaryType, sourceDictionary, state.JsonPath, options); + } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs new file mode 100644 index 0000000..95fa7bd --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Diagnostics; +using System.Text.Json.Serialization.Policies; + +namespace System.Text.Json.Serialization.Converters +{ + internal sealed class DefaultImmutableDictionaryConverter : JsonDictionaryConverter + { + private const string ImmutableDictionaryGenericTypeName = "System.Collections.Immutable.ImmutableDictionary`2"; + private const string ImmutableDictionaryGenericInterfaceTypeName = "System.Collections.Immutable.IImmutableDictionary`2"; + private const string ImmutableSortedDictionaryGenericTypeName = "System.Collections.Immutable.ImmutableSortedDictionary`2"; + + public static void RegisterImmutableDictionary(Type immutableCollectionType, Type elementType, JsonSerializerOptions options) + { + // Get a unique identifier for a delegate which will point to the appropiate CreateRange method. + string delegateKey = DefaultImmutableEnumerableConverter.GetDelegateKey(immutableCollectionType, elementType, out Type underlyingType, out string constructingTypeName); + + // Exit if we have registered this immutable dictionary type. + if (options.CreateRangeDelegatesContainsKey(delegateKey)) + { + return; + } + + // Get the constructing type. + Type constructingType = underlyingType.Assembly.GetType(constructingTypeName); + + // Create a delegate which will point to the CreateRange method. + object createRangeDelegate; + createRangeDelegate = options.ClassMaterializerStrategy.ImmutableDictionaryCreateRange(constructingType, elementType); + + // Cache the delegate + options.TryAddCreateRangeDelegate(delegateKey, createRangeDelegate); + } + + public static bool IsImmutableDictionary(Type type) + { + if (!type.IsGenericType) + { + return false; + } + + switch (type.GetGenericTypeDefinition().FullName) + { + case ImmutableDictionaryGenericTypeName: + case ImmutableDictionaryGenericInterfaceTypeName: + case ImmutableSortedDictionaryGenericTypeName: + return true; + default: + return false; + } + } + + public override IDictionary CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options) + { + Type immutableCollectionType = state.Current.JsonPropertyInfo.RuntimePropertyType; + Type elementType = state.Current.GetElementType(); + + string delegateKey = DefaultImmutableEnumerableConverter.GetDelegateKey(immutableCollectionType, elementType, out _, out _); + + JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo; + JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementClassInfo, options); + return propertyInfo.CreateImmutableDictionaryInstance(immutableCollectionType, delegateKey, sourceDictionary, state.JsonPath, options); + } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs similarity index 53% rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableConverter.cs rename to src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs index d4a06b6..6fc7e90 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs @@ -3,18 +3,14 @@ // See the LICENSE file in the project root for more information. using System.Collections; -using System.Collections.Generic; -using System.Collections.Concurrent; using System.Diagnostics; using System.Text.Json.Serialization.Policies; namespace System.Text.Json.Serialization.Converters { // This converter returns enumerables in the System.Collections.Immutable namespace. - internal sealed class DefaultImmutableConverter : JsonEnumerableConverter + internal sealed class DefaultImmutableEnumerableConverter : JsonEnumerableConverter { - public const string ImmutableNamespace = "System.Collections.Immutable"; - private const string ImmutableListTypeName = "System.Collections.Immutable.ImmutableList"; private const string ImmutableListGenericTypeName = "System.Collections.Immutable.ImmutableList`1"; private const string ImmutableListGenericInterfaceTypeName = "System.Collections.Immutable.IImmutableList`1"; @@ -41,84 +37,58 @@ namespace System.Text.Json.Serialization.Converters private const string ImmutableSortedDictionaryTypeName = "System.Collections.Immutable.ImmutableSortedDictionary"; private const string ImmutableSortedDictionaryGenericTypeName = "System.Collections.Immutable.ImmutableSortedDictionary`2"; - internal delegate object ImmutableCreateRangeDelegate(IEnumerable items); - internal delegate object ImmutableDictCreateRangeDelegate(IEnumerable> items); - - private static ConcurrentDictionary s_createRangeDelegates = new ConcurrentDictionary(); - - private static string GetConstructingTypeName(string immutableCollectionTypeName) + public static string GetDelegateKey( + Type immutableCollectionType, + Type elementType, + out Type underlyingType, + out string constructingTypeName) { - switch (immutableCollectionTypeName) + // Use the generic type definition of the immutable collection to determine an appropriate constructing type, + // i.e. a type that we can invoke the `CreateRange` method on, which returns an assignable immutable collection. + underlyingType = immutableCollectionType.GetGenericTypeDefinition(); + + switch (underlyingType.FullName) { case ImmutableListGenericTypeName: case ImmutableListGenericInterfaceTypeName: - return ImmutableListTypeName; + constructingTypeName = ImmutableListTypeName; + break; case ImmutableStackGenericTypeName: case ImmutableStackGenericInterfaceTypeName: - return ImmutableStackTypeName; + constructingTypeName = ImmutableStackTypeName; + break; case ImmutableQueueGenericTypeName: case ImmutableQueueGenericInterfaceTypeName: - return ImmutableQueueTypeName; + constructingTypeName = ImmutableQueueTypeName; + break; case ImmutableSortedSetGenericTypeName: - return ImmutableSortedSetTypeName; + constructingTypeName = ImmutableSortedSetTypeName; + break; case ImmutableHashSetGenericTypeName: case ImmutableSetGenericInterfaceTypeName: - return ImmutableHashSetTypeName; + constructingTypeName = ImmutableHashSetTypeName; + break; case ImmutableDictionaryGenericTypeName: case ImmutableDictionaryGenericInterfaceTypeName: - return ImmutableDictionaryTypeName; + constructingTypeName = ImmutableDictionaryTypeName; + break; case ImmutableSortedDictionaryGenericTypeName: - return ImmutableSortedDictionaryTypeName; + constructingTypeName = ImmutableSortedDictionaryTypeName; + break; default: - // TODO: Refactor exception throw following serialization exception changes. - throw new NotSupportedException(SR.Format(SR.DeserializeTypeNotSupported, immutableCollectionTypeName)); + throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(immutableCollectionType, null, null); } - } - - private static string GetDelegateKey( - Type immutableCollectionType, - Type elementType, - out Type underlyingType, - out string constructingTypeName) - { - // Use the generic type definition of the immutable collection to determine an appropriate constructing type, - // i.e. a type that we can invoke the `CreateRange` method on, which returns an assignable immutable collection. - underlyingType = immutableCollectionType.GetGenericTypeDefinition(); - constructingTypeName = GetConstructingTypeName(underlyingType.FullName); return $"{constructingTypeName}:{elementType.FullName}"; } - internal static bool TypeIsImmutableDictionary(Type type) - { - if (!type.IsGenericType) - { - return false; - } - - switch (type.GetGenericTypeDefinition().FullName) - { - case ImmutableDictionaryGenericTypeName: - case ImmutableDictionaryGenericInterfaceTypeName: - case ImmutableSortedDictionaryGenericTypeName: - return true; - default: - return false; - } - } - - internal static bool TryGetCreateRangeDelegate(string delegateKey, out object createRangeDelegate) - { - return s_createRangeDelegates.TryGetValue(delegateKey, out createRangeDelegate) && createRangeDelegate != null; - } - - internal static void RegisterImmutableCollection(Type immutableCollectionType, Type elementType, JsonSerializerOptions options) + public static void RegisterImmutableCollection(Type immutableCollectionType, Type elementType, JsonSerializerOptions options) { // Get a unique identifier for a delegate which will point to the appropiate CreateRange method. string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out Type underlyingType, out string constructingTypeName); // Exit if we have registered this immutable collection type. - if (s_createRangeDelegates.ContainsKey(delegateKey)) + if (options.CreateRangeDelegatesContainsKey(delegateKey)) { return; } @@ -131,29 +101,7 @@ namespace System.Text.Json.Serialization.Converters createRangeDelegate = options.ClassMaterializerStrategy.ImmutableCollectionCreateRange(constructingType, elementType); // Cache the delegate - s_createRangeDelegates.TryAdd(delegateKey, createRangeDelegate); - } - - internal static void RegisterImmutableDictionary(Type immutableCollectionType, Type elementType, JsonSerializerOptions options) - { - // Get a unique identifier for a delegate which will point to the appropiate CreateRange method. - string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out Type underlyingType, out string constructingTypeName); - - // Exit if we have registered this immutable collection type. - if (s_createRangeDelegates.ContainsKey(delegateKey)) - { - return; - } - - // Get the constructing type. - Type constructingType = underlyingType.Assembly.GetType(constructingTypeName); - - // Create a delegate which will point to the CreateRange method. - object createRangeDelegate; - createRangeDelegate = options.ClassMaterializerStrategy.ImmutableDictionaryCreateRange(constructingType, elementType); - - // Cache the delegate - s_createRangeDelegates.TryAdd(delegateKey, createRangeDelegate); + options.TryAddCreateRangeDelegate(delegateKey, createRangeDelegate); } public override IEnumerable CreateFromList(ref ReadStack state, IList sourceList, JsonSerializerOptions options) @@ -162,24 +110,10 @@ namespace System.Text.Json.Serialization.Converters Type elementType = state.Current.GetElementType(); string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out _, out _); - Debug.Assert(s_createRangeDelegates.ContainsKey(delegateKey)); - - JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo; - JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementClassInfo, options); - return propertyInfo.CreateImmutableCollectionFromList(immutableCollectionType, delegateKey, sourceList, state.JsonPath); - } - - internal IDictionary CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options) - { - Type immutableCollectionType = state.Current.JsonPropertyInfo.RuntimePropertyType; - Type elementType = state.Current.GetElementType(); - - string delegateKey = GetDelegateKey(immutableCollectionType, elementType, out _, out _); - Debug.Assert(s_createRangeDelegates.ContainsKey(delegateKey)); JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo; JsonPropertyInfo propertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementClassInfo, options); - return propertyInfo.CreateImmutableCollectionFromDictionary(immutableCollectionType, delegateKey, sourceDictionary, state.JsonPath); + return propertyInfo.CreateImmutableCollectionInstance(immutableCollectionType, delegateKey, sourceList, state.JsonPath, options); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/DefaultIDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/DefaultIDictionaryConverter.cs new file mode 100644 index 0000000..720562f --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/DefaultIDictionaryConverter.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Text.Json.Serialization.Policies; + +namespace System.Text.Json.Serialization.Converters +{ + internal sealed class DefaultIDictionaryConverter : JsonDictionaryConverter + { + public override IDictionary CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options) + { + Type enumerableType = state.Current.JsonPropertyInfo.RuntimePropertyType; + return (IDictionary)Activator.CreateInstance(enumerableType, sourceDictionary); + } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs index 7244fa5..3c535a4 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections; using System.Reflection; using System.Text.Json.Serialization; @@ -29,7 +30,7 @@ namespace System.Text.Json if (propertyType.IsInterface && jsonInfo.ClassType == ClassType.Dictionary) { // If a polymorphic case, we have to wait until run-time values are processed. - if (jsonInfo.ElementClassInfo.ClassType != ClassType.Unknown) + if (jsonInfo.ElementClassInfo.ClassType != ClassType.Unknown || propertyType == typeof(IDictionary)) { Type newPropertyType = jsonInfo.ElementClassInfo.GetPolicyProperty().GetDictionaryConcreteType(); if (propertyType != newPropertyType) @@ -38,6 +39,21 @@ namespace System.Text.Json } } } + else if (jsonInfo.ClassType == ClassType.Enumerable && + !propertyType.IsArray && + (IsDeserializedByAssigningFromList(propertyType) || IsSetInterface(propertyType))) + { + JsonClassInfo elementClassInfo = jsonInfo.ElementClassInfo; + JsonPropertyInfo elementPropertyInfo = options.GetJsonPropertyInfoFromClassInfo(elementClassInfo, options); + + // Get a runtime type for the property. e.g. ISet -> HashSet, ICollection -> List + // We use the element's JsonPropertyInfo so we can utilize the generic support. + Type newPropertyType = elementPropertyInfo.GetConcreteType(propertyType); + if ((propertyType != newPropertyType) && propertyType.IsAssignableFrom(newPropertyType)) + { + jsonInfo = CreateProperty(propertyType, newPropertyType, propertyInfo, classType, options); + } + } if (propertyInfo != null) { @@ -65,7 +81,8 @@ namespace System.Text.Json { case ClassType.Enumerable: case ClassType.Dictionary: - case ClassType.ImmutableDictionary: + case ClassType.IDictionaryConstructible: + case ClassType.KeyValuePair: case ClassType.Unknown: collectionElementType = GetElementType(runtimePropertyType, parentClassType, propertyInfo); break; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs index 7e63958..664ca8a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs @@ -94,35 +94,37 @@ namespace System.Text.Json switch (ClassType) { case ClassType.Object: - var propertyNames = new HashSet(StringComparer.Ordinal); - - foreach (PropertyInfo propertyInfo in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { - // Ignore indexers - if (propertyInfo.GetIndexParameters().Length > 0) - { - continue; - } + var propertyNames = new HashSet(StringComparer.Ordinal); - // For now we only support public getters\setters - if (propertyInfo.GetMethod?.IsPublic == true || - propertyInfo.SetMethod?.IsPublic == true) + foreach (PropertyInfo propertyInfo in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { - JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options); - - Debug.Assert(jsonPropertyInfo.NameUsedToCompareAsString != null); - - // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception. - if (!propertyNames.Add(jsonPropertyInfo.NameUsedToCompareAsString)) + // Ignore indexers + if (propertyInfo.GetIndexParameters().Length > 0) { - ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(this, jsonPropertyInfo); + continue; } - jsonPropertyInfo.ClearUnusedValuesAfterAdd(); + // For now we only support public getters\setters + if (propertyInfo.GetMethod?.IsPublic == true || + propertyInfo.SetMethod?.IsPublic == true) + { + JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options); + + Debug.Assert(jsonPropertyInfo.NameUsedToCompareAsString != null); + + // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception. + if (!propertyNames.Add(jsonPropertyInfo.NameUsedToCompareAsString)) + { + ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(this, jsonPropertyInfo); + } + + jsonPropertyInfo.ClearUnusedValuesAfterAdd(); + } } - } - DetermineExtensionDataProperty(); + DetermineExtensionDataProperty(); + } break; case ClassType.Enumerable: case ClassType.Dictionary: @@ -138,7 +140,7 @@ namespace System.Text.Json ElementClassInfo = options.GetOrAddClass(elementType); } break; - case ClassType.ImmutableDictionary: + case ClassType.IDictionaryConstructible: { // Add a single property that maps to the class type so we can have policies applied. AddPolicyProperty(type, options); @@ -152,6 +154,39 @@ namespace System.Text.Json ElementClassInfo = options.GetOrAddClass(elementType); } break; + // TODO: Utilize converter mechanism to handle (de)serialization of KeyValuePair + // when it goes through: https://github.com/dotnet/corefx/issues/36639. + case ClassType.KeyValuePair: + { + // For deserialization, we treat it as ClassType.IDictionaryConstructible so we can parse it like a dictionary + // before using converter-like logic to create a KeyValuePair instance. + + // Add a single property that maps to the class type so we can have policies applied. + AddPolicyProperty(type, options); + + Type elementType = GetElementType(type, parentType: null, memberInfo: null); + + // Make this Dictionary to accomodate input of form {"Key": "MyKey", "Value": 1}. + CreateObject = options.ClassMaterializerStrategy.CreateConstructor(typeof(Dictionary)); + + // Create a ClassInfo that maps to the element type which is used for deserialization and policies. + ElementClassInfo = options.GetOrAddClass(elementType); + + // For serialization, we treat it like ClassType.Object to utilize the public getters. + + // Add Key property + PropertyInfo propertyInfo = type.GetProperty("Key", BindingFlags.Public | BindingFlags.Instance); + JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options); + Debug.Assert(jsonPropertyInfo.NameUsedToCompareAsString != null); + jsonPropertyInfo.ClearUnusedValuesAfterAdd(); + + // Add Value property. + propertyInfo = type.GetProperty("Value", BindingFlags.Public | BindingFlags.Instance); + jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options); + Debug.Assert(jsonPropertyInfo.NameUsedToCompareAsString != null); + jsonPropertyInfo.ClearUnusedValuesAfterAdd(); + } + break; case ClassType.Value: case ClassType.Unknown: // Add a single property that maps to the class type so we can have policies applied. @@ -289,6 +324,13 @@ namespace System.Text.Json return _propertyRefs[0].Info; } + internal JsonPropertyInfo GetPolicyPropertyOfKeyValuePair() + { + // We have 3 here. One for the KeyValuePair itself, one for Key property, and one for the Value property. + Debug.Assert(_propertyRefs.Count == 3); + return _propertyRefs[0].Info; + } + internal JsonPropertyInfo GetProperty(int index) { Debug.Assert(index < _propertyRefs.Count); @@ -374,10 +416,10 @@ namespace System.Text.Json return key; } - // Return the element type of the IEnumerable, or return null if not an IEnumerable. + // Return the element type of the IEnumerable or KeyValuePair, or return null if not an IEnumerable or KayValuePair. public static Type GetElementType(Type propertyType, Type parentType, MemberInfo memberInfo) { - if (!typeof(IEnumerable).IsAssignableFrom(propertyType)) + if (!typeof(IEnumerable).IsAssignableFrom(propertyType) && !IsKeyValuePair(propertyType)) { return null; } @@ -395,7 +437,7 @@ namespace System.Text.Json Type[] args = propertyType.GetGenericArguments(); ClassType classType = GetClassType(propertyType); - if ((classType == ClassType.Dictionary || classType == ClassType.ImmutableDictionary) && + if ((classType == ClassType.Dictionary || classType == ClassType.IDictionaryConstructible || classType == ClassType.KeyValuePair) && args.Length >= 2 && // It is >= 2 in case there is a IDictionary. args[0].UnderlyingSystemType == typeof(string)) { @@ -408,10 +450,18 @@ namespace System.Text.Json } } + if (propertyType.IsAssignableFrom(typeof(IList)) || + propertyType.IsAssignableFrom(typeof(IDictionary)) || + IsDeserializedByConstructingWithIList(propertyType) || + IsDeserializedByConstructingWithIDictionary(propertyType)) + { + return typeof(object); + } + throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(propertyType, parentType, memberInfo); } - internal static ClassType GetClassType(Type type) + public static ClassType GetClassType(Type type) { Debug.Assert(type != null); @@ -425,18 +475,24 @@ namespace System.Text.Json return ClassType.Value; } - if (DefaultImmutableConverter.TypeIsImmutableDictionary(type)) + if (DefaultImmutableDictionaryConverter.IsImmutableDictionary(type) || + IsDeserializedByConstructingWithIDictionary(type)) { - return ClassType.ImmutableDictionary; + return ClassType.IDictionaryConstructible; } - if (typeof(IDictionary).IsAssignableFrom(type) || + if (typeof(IDictionary).IsAssignableFrom(type) || (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IDictionary<,>) || type.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)))) { return ClassType.Dictionary; } + if (IsKeyValuePair(type)) + { + return ClassType.KeyValuePair; + } + if (typeof(IEnumerable).IsAssignableFrom(type)) { return ClassType.Enumerable; @@ -449,5 +505,98 @@ namespace System.Text.Json return ClassType.Object; } + + public const string ImmutableNamespaceName = "System.Collections.Immutable"; + + private const string EnumerableGenericInterfaceTypeName = "System.Collections.Generic.IEnumerable`1"; + private const string EnumerableInterfaceTypeName = "System.Collections.IEnumerable"; + + private const string ListGenericInterfaceTypeName = "System.Collections.Generic.IList`1"; + private const string ListInterfaceTypeName = "System.Collections.IList"; + + private const string CollectionGenericInterfaceTypeName = "System.Collections.Generic.ICollection`1"; + private const string CollectionInterfaceTypeName = "System.Collections.ICollection"; + + private const string ReadOnlyListGenericInterfaceTypeName = "System.Collections.Generic.IReadOnlyList`1"; + + private const string ReadOnlyCollectionGenericInterfaceTypeName = "System.Collections.Generic.IReadOnlyCollection`1"; + + public const string HashtableTypeName = "System.Collections.Hashtable"; + public const string SortedListTypeName = "System.Collections.SortedList"; + + public const string StackTypeName = "System.Collections.Stack"; + public const string QueueTypeName = "System.Collections.Queue"; + public const string ArrayListTypeName = "System.Collections.ArrayList"; + + public static bool IsDeserializedByAssigningFromList(Type type) + { + if (type.IsGenericType) + { + switch (type.GetGenericTypeDefinition().FullName) + { + case EnumerableGenericInterfaceTypeName: + case ListGenericInterfaceTypeName: + case CollectionGenericInterfaceTypeName: + case ReadOnlyListGenericInterfaceTypeName: + case ReadOnlyCollectionGenericInterfaceTypeName: + return true; + default: + return false; + } + } + else + { + switch (type.FullName) + { + case EnumerableInterfaceTypeName: + case ListInterfaceTypeName: + case CollectionInterfaceTypeName: + return true; + default: + return false; + } + } + } + + public static bool IsSetInterface(Type type) + { + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ISet<>); + } + + public static bool HasConstructorThatTakesGenericIEnumerable(Type type) + { + Type elementType = GetElementType(type, parentType: null, memberInfo: null); + return type.GetConstructor(new Type[] { typeof(List<>).MakeGenericType(elementType) }) != null; + } + + public static bool IsDeserializedByConstructingWithIList(Type type) + { + switch (type.FullName) + { + case StackTypeName: + case QueueTypeName: + case ArrayListTypeName: + return true; + default: + return false; + } + } + + public static bool IsDeserializedByConstructingWithIDictionary(Type type) + { + switch (type.FullName) + { + case HashtableTypeName: + case SortedListTypeName: + return true; + default: + return false; + } + } + + public static bool IsKeyValuePair(Type type) + { + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>); + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs new file mode 100644 index 0000000..98884a3 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonDictionaryConverter.cs @@ -0,0 +1,13 @@ +// 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.Policies +{ + internal abstract class JsonDictionaryConverter + { + public abstract IDictionary CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options); + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs index 3d2d587..fa18769 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Collections; -using System.Collections.Generic; namespace System.Text.Json.Serialization.Policies { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs index 0f34a21..c190a61 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Collections; -using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Text.Json.Serialization; @@ -17,9 +16,11 @@ namespace System.Text.Json { // Cache the converters so they don't get created for every enumerable property. private static readonly JsonEnumerableConverter s_jsonArrayConverter = new DefaultArrayConverter(); - private static readonly JsonEnumerableConverter s_jsonEnumerableConverter = new DefaultEnumerableConverter(); - private static readonly JsonEnumerableConverter s_jsonIEnumerableConstuctibleConverter = new DefaultIEnumerableConstructibleConverter(); - private static readonly JsonEnumerableConverter s_jsonImmutableConverter = new DefaultImmutableConverter(); + private static readonly JsonEnumerableConverter s_jsonICollectionConverter = new DefaultICollectionConverter(); + private static readonly JsonDictionaryConverter s_jsonIDictionaryConverter = new DefaultIDictionaryConverter(); + private static readonly JsonEnumerableConverter s_jsonImmutableEnumerableConverter = new DefaultImmutableEnumerableConverter(); + private static readonly JsonDictionaryConverter s_jsonImmutableDictionaryConverter = new DefaultImmutableDictionaryConverter(); + private JsonClassInfo _runtimeClassInfo; @@ -77,7 +78,10 @@ namespace System.Text.Json { if (_elementClassInfo == null && _elementType != null) { - Debug.Assert(ClassType == ClassType.Enumerable || ClassType == ClassType.Dictionary || ClassType == ClassType.ImmutableDictionary); + Debug.Assert(ClassType == ClassType.Enumerable || + ClassType == ClassType.Dictionary || + ClassType == ClassType.IDictionaryConstructible || + ClassType == ClassType.KeyValuePair); _elementClassInfo = Options.GetOrAddClass(_elementType); } @@ -105,7 +109,9 @@ namespace System.Text.Json } public bool CanBeNull { get; private set; } + public JsonEnumerableConverter EnumerableConverter { get; private set; } + public JsonDictionaryConverter DictionaryConverter { get; private set; } public bool IsNullableType { get; private set; } @@ -180,7 +186,10 @@ namespace System.Text.Json private void DetermineSerializationCapabilities() { - if (ClassType != ClassType.Enumerable && ClassType != ClassType.Dictionary && ClassType != ClassType.ImmutableDictionary) + if (ClassType != ClassType.Enumerable && + ClassType != ClassType.Dictionary && + ClassType != ClassType.IDictionaryConstructible && + ClassType != ClassType.KeyValuePair) { // We serialize if there is a getter + not ignoring readonly properties. ShouldSerialize = HasGetter && (HasSetter || !Options.IgnoreReadOnlyProperties); @@ -215,36 +224,34 @@ namespace System.Text.Json { EnumerableConverter = s_jsonArrayConverter; } - else if (ClassType == ClassType.ImmutableDictionary) + else if (ClassType == ClassType.IDictionaryConstructible) { - DefaultImmutableConverter.RegisterImmutableDictionary( - RuntimePropertyType, JsonClassInfo.GetElementType(RuntimePropertyType, parentType: null, memberInfo: null), Options); - EnumerableConverter = s_jsonImmutableConverter; + if (RuntimePropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName)) + { + DefaultImmutableDictionaryConverter.RegisterImmutableDictionary( + RuntimePropertyType, JsonClassInfo.GetElementType(RuntimePropertyType, ParentClassType, PropertyInfo), Options); + DictionaryConverter = s_jsonImmutableDictionaryConverter; + } + else if (JsonClassInfo.IsDeserializedByConstructingWithIDictionary(RuntimePropertyType)) + { + DictionaryConverter = s_jsonIDictionaryConverter; + } } else if (typeof(IEnumerable).IsAssignableFrom(RuntimePropertyType)) { - Type elementType = JsonClassInfo.GetElementType(RuntimePropertyType, ParentClassType, PropertyInfo); - - // If the property type only has interface(s) exposed by JsonEnumerableT then use JsonEnumerableT as the converter. - if (RuntimePropertyType.IsAssignableFrom(typeof(JsonEnumerableT<>).MakeGenericType(elementType))) - { - EnumerableConverter = s_jsonEnumerableConverter; - } - // Else if IList can't be assigned from the property type (we populate and return an IList directly) - // and the type can be constructed with an IEnumerable, then use the - // IEnumerableConstructible converter to create the instance. - else if (!typeof(IList).IsAssignableFrom(RuntimePropertyType) && - RuntimePropertyType.GetConstructor(new Type[] { typeof(List<>).MakeGenericType(elementType) }) != null) + if (JsonClassInfo.IsDeserializedByConstructingWithIList(RuntimePropertyType) || + (!typeof(IList).IsAssignableFrom(RuntimePropertyType) && JsonClassInfo.HasConstructorThatTakesGenericIEnumerable(RuntimePropertyType))) { - EnumerableConverter = s_jsonIEnumerableConstuctibleConverter; + EnumerableConverter = s_jsonICollectionConverter; } - // Else if it's a System.Collections.Immutable type with one generic argument. else if (RuntimePropertyType.IsGenericType && - RuntimePropertyType.FullName.StartsWith(DefaultImmutableConverter.ImmutableNamespace) && + RuntimePropertyType.FullName.StartsWith(JsonClassInfo.ImmutableNamespaceName) && RuntimePropertyType.GetGenericArguments().Length == 1) { - DefaultImmutableConverter.RegisterImmutableCollection(RuntimePropertyType, elementType, Options); - EnumerableConverter = s_jsonImmutableConverter; + DefaultImmutableEnumerableConverter.RegisterImmutableCollection(RuntimePropertyType, + JsonClassInfo.GetElementType(RuntimePropertyType, ParentClassType, PropertyInfo), + Options); + EnumerableConverter = s_jsonImmutableEnumerableConverter; } } } @@ -292,16 +299,22 @@ namespace System.Text.Json return (TAttribute)propertyInfo?.GetCustomAttribute(typeof(TAttribute), inherit: false); } - public abstract IEnumerable CreateImmutableCollectionFromList(Type collectionType, string delegateKey, IList sourceList, string propertyPath); + public abstract IEnumerable CreateIEnumerableInstance(Type parentType, IList sourceList, string jsonPath, JsonSerializerOptions options); - public abstract IDictionary CreateImmutableCollectionFromDictionary(Type collectionType, string delegateKey, IDictionary sourceDictionary, string propertyPath); + public abstract IDictionary CreateIDictionaryInstance(Type parentType, IDictionary sourceDictionary, string jsonPath, JsonSerializerOptions options); - public abstract IEnumerable CreateIEnumerableConstructibleType(Type enumerableType, IList sourceList); + public abstract IEnumerable CreateImmutableCollectionInstance(Type collectionType, string delegateKey, IList sourceList, string propertyPath, JsonSerializerOptions options); + + public abstract IDictionary CreateImmutableDictionaryInstance(Type collectionType, string delegateKey, IDictionary sourceDictionary, string propertyPath, JsonSerializerOptions options); public abstract IList CreateConverterList(); + public abstract ValueType CreateKeyValuePairInstance(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options); + public abstract Type GetDictionaryConcreteType(); + public abstract Type GetConcreteType(Type type); + public abstract void Read(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader); public abstract void ReadEnumerable(JsonTokenType tokenType, ref ReadStack state, ref Utf8JsonReader reader); public abstract void SetValueAsObject(object obj, object value); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs index ca5dff0..4ff9654 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs @@ -93,44 +93,133 @@ namespace System.Text.Json return typeof(Dictionary); } + public override Type GetConcreteType(Type parentType) + { + if (JsonClassInfo.IsDeserializedByAssigningFromList(parentType)) + { + return typeof(List); + } + else if (JsonClassInfo.IsSetInterface(parentType)) + { + return typeof(HashSet); + } + + return parentType; + } + + public override IEnumerable CreateIEnumerableInstance(Type parentType, IList sourceList, string jsonPath, JsonSerializerOptions options) + { + 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, unless we add a ref to System.Collections.NonGeneric. + else + { + return (IEnumerable)Activator.CreateInstance(parentType, sourceList); + } + } + } + + public override IDictionary CreateIDictionaryInstance(Type parentType, IDictionary sourceDictionary, string jsonPath, JsonSerializerOptions options) + { + 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); + } + } + // Creates an IEnumerable and populates it with the items in the // sourceList argument then uses the delegateKey argument to identify the appropriate cached // CreateRange method to create and return the desired immutable collection type. - public override IEnumerable CreateImmutableCollectionFromList(Type collectionType, string delegateKey, IList sourceList, string propertyPath) + public override IEnumerable CreateImmutableCollectionInstance(Type collectionType, string delegateKey, IList sourceList, string jsonPath, JsonSerializerOptions options) { - if (!DefaultImmutableConverter.TryGetCreateRangeDelegate(delegateKey, out object createRangeDelegateObj)) + if (!options.TryGetCreateRangeDelegate(delegateKey, out object createRangeDelegateObj)) { - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, propertyPath); + ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, jsonPath); } - DefaultImmutableConverter.ImmutableCreateRangeDelegate createRangeDelegate = ( - (DefaultImmutableConverter.ImmutableCreateRangeDelegate)createRangeDelegateObj); + JsonSerializerOptions.ImmutableCreateRangeDelegate createRangeDelegate = ( + (JsonSerializerOptions.ImmutableCreateRangeDelegate)createRangeDelegateObj); - return (IEnumerable)createRangeDelegate.Invoke(CreateGenericIEnumerableFromList(sourceList)); + return (IEnumerable)createRangeDelegate.Invoke(CreateGenericTRuntimePropertyIEnumerable(sourceList)); } // Creates an IEnumerable and populates it with the items in the // sourceList argument then uses the delegateKey argument to identify the appropriate cached // CreateRange method to create and return the desired immutable collection type. - public override IDictionary CreateImmutableCollectionFromDictionary(Type collectionType, string delegateKey, IDictionary sourceDictionary, string propertyPath) + public override IDictionary CreateImmutableDictionaryInstance(Type collectionType, string delegateKey, IDictionary sourceDictionary, string jsonPath, JsonSerializerOptions options) { - if (!DefaultImmutableConverter.TryGetCreateRangeDelegate(delegateKey, out object createRangeDelegateObj)) + if (!options.TryGetCreateRangeDelegate(delegateKey, out object createRangeDelegateObj)) { - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, propertyPath); + ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, jsonPath); } - DefaultImmutableConverter.ImmutableDictCreateRangeDelegate createRangeDelegate = ( - (DefaultImmutableConverter.ImmutableDictCreateRangeDelegate)createRangeDelegateObj); + JsonSerializerOptions.ImmutableDictCreateRangeDelegate createRangeDelegate = ( + (JsonSerializerOptions.ImmutableDictCreateRangeDelegate)createRangeDelegateObj); return (IDictionary)createRangeDelegate.Invoke(CreateGenericIEnumerableFromDictionary(sourceDictionary)); } - public override IEnumerable CreateIEnumerableConstructibleType(Type enumerableType, IList sourceList) + public override ValueType CreateKeyValuePairInstance(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options) { - return (IEnumerable)Activator.CreateInstance(enumerableType, CreateGenericIEnumerableFromList(sourceList)); + Type enumerableType = state.Current.JsonPropertyInfo.RuntimePropertyType; + + // Form {"MyKey": 1}. + if (sourceDictionary.Count == 1) + { + IDictionaryEnumerator enumerator = sourceDictionary.GetEnumerator(); + enumerator.MoveNext(); + return new KeyValuePair((string)enumerator.Key, (TRuntimeProperty)enumerator.Value); + } + // Form {"Key": "MyKey", "Value": 1}. + else if (sourceDictionary.Count == 2 && + sourceDictionary["Key"] is string key && + sourceDictionary["Value"] is TRuntimeProperty value + ) + { + return new KeyValuePair(key, value); + } + + throw ThrowHelper.GetJsonException_DeserializeUnableToConvertValue(enumerableType, state.JsonPath); } - private IEnumerable CreateGenericIEnumerableFromList(IList sourceList) + private IEnumerable CreateGenericTRuntimePropertyIEnumerable(IList sourceList) { foreach (object item in sourceList) { @@ -138,6 +227,14 @@ namespace System.Text.Json } } + private IEnumerable CreateGenericTDeclaredPropertyIEnumerable(IList sourceList) + { + foreach (object item in sourceList) + { + yield return (TDeclaredProperty)item; + } + } + private IEnumerable> CreateGenericIEnumerableFromDictionary(IDictionary sourceDictionary) { foreach (DictionaryEntry item in sourceDictionary) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs index 9914128..bcf6c6a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Text.Json.Serialization.Converters; +using System.Text.Json.Serialization.Policies; namespace System.Text.Json { @@ -52,8 +54,7 @@ namespace System.Text.Json { Debug.Assert(ShouldDeserialize); - // We need a property in order to a apply a value in a dictionary. - if (state.Current.KeyName == null && (state.Current.IsProcessingDictionary || state.Current.IsProcessingImmutableDictionary)) + if (state.Current.KeyName == null && (state.Current.IsProcessingDictionary || state.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair)) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath); return; @@ -68,10 +69,41 @@ namespace System.Text.Json if (ValueConverter == null || !ValueConverter.TryRead(RuntimePropertyType, ref reader, out TRuntimeProperty value)) { + if (state.Current.IsProcessingKeyValuePair && state.Current.KeyName == "Key") + { + // Handle the special case where the input KeyValuePair is of form {"Key": "MyKey", "Value": 1} + // (as opposed to form {"MyKey": 1}) and the value type is not string. + // If we have one, the ValueConverter failed to read the current token because it should be of type string + // (we only support string keys) but we initially tried to read it as type TRuntimeProperty. + // We have TRuntimeProperty not string because for deserialization, we parse the KeyValuePair as a + // dictionary before creating a KeyValuePair instance in a converter-like manner with the parsed values. + // Because it's dictionary-like parsing, we set the element type of the dictionary earlier on to the KeyValuePair's value + // type, which led us here. + // If there's no ValueConverter, the runtime type of the KeyValuePair's value is probably an object, dictionary or enumerable. + JsonValueConverter stringConverter = DefaultConverters.s_converter; + if (!stringConverter.TryRead(typeof(string), ref reader, out string strValue)) + { + ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(typeof(string), reader, state.JsonPath); + } + + object objValue = strValue; + + JsonSerializer.ApplyValueToEnumerable(ref objValue, ref state, ref reader); + return; + } + ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.JsonPath); return; } + if (state.Current.IsProcessingKeyValuePair) + { + // The value is being applied to a Dictionary, so we need to cast to object here. + object objValue = value; + JsonSerializer.ApplyValueToEnumerable(ref objValue, ref state, ref reader); + return; + } + JsonSerializer.ApplyValueToEnumerable(ref value, ref state, ref reader); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs index 7ee0975..04bd680 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs @@ -37,7 +37,7 @@ namespace System.Text.Json // Verify that we don't have a multidimensional array. Type arrayType = jsonPropertyInfo.RuntimePropertyType; - if (!typeof(IEnumerable).IsAssignableFrom(arrayType) || (arrayType.IsArray && arrayType.GetArrayRank() > 1)) + if (!state.Current.IsProcessingKeyValuePair && !typeof(IEnumerable).IsAssignableFrom(arrayType) || (arrayType.IsArray && arrayType.GetArrayRank() > 1)) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(arrayType, reader, state.JsonPath); } @@ -128,7 +128,7 @@ namespace System.Text.Json state.Current.ReturnValue = value; return true; } - else if (state.Current.IsEnumerable || state.Current.IsDictionary || state.Current.IsImmutableDictionary) + else if (state.Current.IsEnumerable || state.Current.IsDictionary || state.Current.IsIDictionaryConstructible) { // Returning a non-converted list. return true; @@ -207,7 +207,10 @@ namespace System.Text.Json ThrowHelper.ThrowJsonException_DeserializeDuplicateKey(key, reader, state.JsonPath); } } - else if (state.Current.IsImmutableDictionary || (state.Current.IsImmutableDictionaryProperty && !setPropertyDirectly)) + else if (state.Current.IsIDictionaryConstructible || + (state.Current.IsIDictionaryConstructibleProperty && !setPropertyDirectly) || + state.Current.IsKeyValuePair || + (state.Current.IsKeyValuePairProperty && !setPropertyDirectly)) { Debug.Assert(state.Current.TempDictionaryValues != null); IDictionary dictionary = (IDictionary)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.TempDictionaryValues); @@ -286,7 +289,7 @@ namespace System.Text.Json ThrowHelper.ThrowJsonException_DeserializeDuplicateKey(key, reader, state.JsonPath); } } - else if (state.Current.IsProcessingImmutableDictionary) + else if (state.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair) { Debug.Assert(state.Current.TempDictionaryValues != null); IDictionary dictionary = (IDictionary)state.Current.TempDictionaryValues; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs index 9c7f73d..595be6b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleDictionary.cs @@ -4,7 +4,7 @@ using System.Collections; using System.Diagnostics; -using System.Text.Json.Serialization.Converters; +using System.Text.Json.Serialization.Policies; namespace System.Text.Json { @@ -40,7 +40,7 @@ namespace System.Text.Json JsonClassInfo classInfo = state.Current.JsonClassInfo; - if (state.Current.IsProcessingImmutableDictionary) + if (state.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair) { state.Current.TempDictionaryValues = (IDictionary)classInfo.CreateObject(); } @@ -58,7 +58,7 @@ namespace System.Text.Json state.Current.PropertyInitialized = true; - if (state.Current.IsProcessingImmutableDictionary) + if (state.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair) { JsonClassInfo dictionaryClassInfo = options.GetOrAddClass(jsonPropertyInfo.RuntimePropertyType); state.Current.TempDictionaryValues = (IDictionary)dictionaryClassInfo.CreateObject(); @@ -92,10 +92,31 @@ namespace System.Text.Json // We added the items to the dictionary already. state.Current.ResetProperty(); } - else if (state.Current.IsImmutableDictionaryProperty) + else if (state.Current.IsIDictionaryConstructibleProperty) { Debug.Assert(state.Current.TempDictionaryValues != null); - state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, CreateImmutableDictionaryFromTempValues(ref state, options)); + JsonDictionaryConverter converter = state.Current.JsonPropertyInfo.DictionaryConverter; + state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, converter.CreateFromDictionary(ref state, state.Current.TempDictionaryValues, options)); + state.Current.ResetProperty(); + } + else if (state.Current.IsKeyValuePairProperty) + { + JsonClassInfo elementClassInfo = state.Current.JsonPropertyInfo.ElementClassInfo; + + JsonPropertyInfo propertyInfo; + if (elementClassInfo.ClassType == ClassType.KeyValuePair) + { + propertyInfo = elementClassInfo.GetPolicyPropertyOfKeyValuePair(); + } + else + { + propertyInfo = elementClassInfo.GetPolicyProperty(); + } + + Debug.Assert(state.Current.TempDictionaryValues != null); + state.Current.JsonPropertyInfo.SetValueAsObject( + state.Current.ReturnValue, + propertyInfo.CreateKeyValuePairInstance(ref state, state.Current.TempDictionaryValues, options)); state.Current.ResetProperty(); } else @@ -103,7 +124,27 @@ namespace System.Text.Json object value; if (state.Current.TempDictionaryValues != null) { - value = CreateImmutableDictionaryFromTempValues(ref state, options); + if (state.Current.IsKeyValuePair) + { + JsonClassInfo elementClassInfo = state.Current.JsonClassInfo.ElementClassInfo; + + JsonPropertyInfo propertyInfo; + if (elementClassInfo.ClassType == ClassType.KeyValuePair) + { + propertyInfo = elementClassInfo.GetPolicyPropertyOfKeyValuePair(); + } + else + { + propertyInfo = elementClassInfo.GetPolicyProperty(); + } + + value = propertyInfo.CreateKeyValuePairInstance(ref state, state.Current.TempDictionaryValues, options); + } + else + { + JsonDictionaryConverter converter = state.Current.JsonPropertyInfo.DictionaryConverter; + value = converter.CreateFromDictionary(ref state, state.Current.TempDictionaryValues, options); + } } else { @@ -123,15 +164,5 @@ namespace System.Text.Json } } } - - private static IDictionary CreateImmutableDictionaryFromTempValues(ref ReadStack state, JsonSerializerOptions options) - { - Debug.Assert(state.Current.IsProcessingImmutableDictionary); - - DefaultImmutableConverter converter = (DefaultImmutableConverter)state.Current.JsonPropertyInfo.EnumerableConverter; - Debug.Assert(converter != null); - - return converter.CreateFromDictionary(ref state, state.Current.TempDictionaryValues, options); - } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs index a4ed20e..324166d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs @@ -29,13 +29,13 @@ namespace System.Text.Json ThrowHelper.ThrowJsonException_DeserializeCannotBeNull(reader, state.JsonPath); } - if (state.Current.IsEnumerable || state.Current.IsDictionary || state.Current.IsImmutableDictionary) + if (state.Current.IsEnumerable || state.Current.IsDictionary || state.Current.IsIDictionaryConstructible) { ApplyObjectToEnumerable(null, ref state, ref reader); return false; } - if (state.Current.IsEnumerableProperty || state.Current.IsDictionaryProperty || state.Current.IsImmutableDictionaryProperty) + if (state.Current.IsEnumerableProperty || state.Current.IsDictionaryProperty || state.Current.IsIDictionaryConstructibleProperty) { bool setPropertyToNull = !state.Current.PropertyInitialized; ApplyObjectToEnumerable(null, ref state, ref reader, setPropertyDirectly: setPropertyToNull); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs index 18d3359..421c6da 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs @@ -11,7 +11,7 @@ namespace System.Text.Json { private static void HandleStartObject(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) { - Debug.Assert(!state.Current.IsProcessingDictionary && !state.Current.IsProcessingImmutableDictionary); + Debug.Assert(!state.Current.IsProcessingDictionary && !state.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair); if (state.Current.IsProcessingEnumerable) { @@ -42,7 +42,7 @@ namespace System.Text.Json } } - if (state.Current.IsProcessingImmutableDictionary) + if (state.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair) { state.Current.TempDictionaryValues = (IDictionary)classInfo.CreateObject(); } @@ -54,7 +54,7 @@ namespace System.Text.Json private static void HandleEndObject(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) { - Debug.Assert(!state.Current.IsProcessingDictionary && !state.Current.IsProcessingImmutableDictionary); + Debug.Assert(!state.Current.IsProcessingDictionary && !state.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair); state.Current.JsonClassInfo.UpdateSortedPropertyCache(ref state.Current); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs index 67f1ae2..11fccd5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs @@ -23,7 +23,7 @@ namespace System.Text.Json Debug.Assert(state.Current.ReturnValue != default || state.Current.TempDictionaryValues != default); Debug.Assert(state.Current.JsonClassInfo != default); - if (state.Current.IsProcessingDictionary || state.Current.IsProcessingImmutableDictionary) + if (state.Current.IsProcessingDictionary || state.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair) { if (ReferenceEquals(state.Current.JsonClassInfo.DataExtensionProperty, state.Current.JsonPropertyInfo)) { @@ -47,7 +47,7 @@ namespace System.Text.Json keyName = options.DictionaryKeyPolicy.ConvertName(keyName); } - if (state.Current.IsDictionary || state.Current.IsImmutableDictionary) + if (state.Current.IsDictionary || state.Current.IsIDictionaryConstructible) { state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetPolicyProperty(); } @@ -55,8 +55,10 @@ namespace System.Text.Json Debug.Assert( state.Current.IsDictionary || (state.Current.IsDictionaryProperty && state.Current.JsonPropertyInfo != null) || - state.Current.IsImmutableDictionary || - (state.Current.IsImmutableDictionaryProperty && state.Current.JsonPropertyInfo != null)); + state.Current.IsIDictionaryConstructible || + (state.Current.IsIDictionaryConstructibleProperty && state.Current.JsonPropertyInfo != null) || + state.Current.IsKeyValuePair || + (state.Current.IsKeyValuePairProperty && state.Current.JsonPropertyInfo != null)); state.Current.KeyName = keyName; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs index 44c46e8..4e56059 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs @@ -66,7 +66,7 @@ namespace System.Text.Json break; } } - else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingImmutableDictionary) + else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair) { HandleStartDictionary(options, ref reader, ref readStack); } @@ -81,7 +81,7 @@ namespace System.Text.Json { readStack.Pop(); } - else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingImmutableDictionary) + else if (readStack.Current.IsProcessingDictionary || readStack.Current.IsProcessingIDictionaryConstructibleOrKeyValuePair) { HandleEndDictionary(options, ref reader, ref readStack); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs index a7ee061..91b6bdb 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs @@ -105,7 +105,7 @@ namespace System.Text.Json value = (TProperty)polymorphicEnumerator.Current.Value; key = polymorphicEnumerator.Current.Key; } - else if (current.IsImmutableDictionary || current.IsImmutableDictionaryProperty) + else if (current.IsIDictionaryConstructible || current.IsIDictionaryConstructibleProperty) { value = (TProperty)((DictionaryEntry)current.Enumerator.Current).Value; key = (string)((DictionaryEntry)current.Enumerator.Current).Key; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs index 495d5ce..826843e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs @@ -60,6 +60,12 @@ namespace System.Text.Json // An object or another enumerator requires a new stack frame. object nextValue = state.Current.Enumerator.Current; state.Push(elementClassInfo, nextValue); + + if (elementClassInfo.ClassType == ClassType.KeyValuePair) + { + state.Current.JsonPropertyInfo = elementClassInfo.GetPolicyPropertyOfKeyValuePair(); + state.Current.PropertyIndex++; + } } return false; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs index b5fa6bb..e01d738 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs @@ -51,6 +51,7 @@ namespace System.Text.Json { Debug.Assert( state.Current.JsonClassInfo.ClassType == ClassType.Object || + state.Current.JsonClassInfo.ClassType == ClassType.KeyValuePair || state.Current.JsonClassInfo.ClassType == ClassType.Unknown); JsonPropertyInfo jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(state.Current.PropertyIndex); @@ -105,9 +106,9 @@ namespace System.Text.Json } // A property that returns an immutable dictionary keeps the same stack frame. - if (jsonPropertyInfo.ClassType == ClassType.ImmutableDictionary) + if (jsonPropertyInfo.ClassType == ClassType.IDictionaryConstructible) { - state.Current.IsImmutableDictionaryProperty = true; + state.Current.IsIDictionaryConstructibleProperty = true; bool endOfEnumerable = HandleDictionary(jsonPropertyInfo.ElementClassInfo, options, writer, ref state); if (endOfEnumerable) @@ -136,6 +137,13 @@ namespace System.Text.Json // Set the PropertyInfo so we can obtain the property name in order to write it. state.Current.JsonPropertyInfo = previousPropertyInfo; + + if (state.Current.JsonPropertyInfo.ClassType == ClassType.KeyValuePair) + { + // Advance to the next property, since the first one is the KeyValuePair type itself, + // not its first property (Key or Value). + state.Current.PropertyIndex++; + } } else { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs index 5d7a5dd..4a737e0 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs @@ -33,10 +33,11 @@ namespace System.Text.Json finishedSerializing = true; break; case ClassType.Object: + case ClassType.KeyValuePair: finishedSerializing = WriteObject(options, writer, ref state); break; case ClassType.Dictionary: - case ClassType.ImmutableDictionary: + case ClassType.IDictionaryConstructible: finishedSerializing = HandleDictionary(current.JsonClassInfo.ElementClassInfo, options, writer, ref state); break; default: diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs index 6260191..5fee1f7 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; using System.Collections.Concurrent; using System.Diagnostics; using System.Text.Json.Serialization; @@ -19,6 +20,7 @@ namespace System.Text.Json private readonly ConcurrentDictionary _classes = new ConcurrentDictionary(); private readonly ConcurrentDictionary _objectJsonProperties = new ConcurrentDictionary(); + private static ConcurrentDictionary s_createRangeDelegates = new ConcurrentDictionary(); private ClassMaterializer _classMaterializerStrategy; private JsonNamingPolicy _dictionayKeyPolicy; private JsonNamingPolicy _jsonPropertyNamingPolicy; @@ -306,8 +308,16 @@ namespace System.Text.Json }; } + internal delegate object ImmutableCreateRangeDelegate(IEnumerable items); + internal delegate object ImmutableDictCreateRangeDelegate(IEnumerable> items); + internal JsonPropertyInfo GetJsonPropertyInfoFromClassInfo(JsonClassInfo classInfo, JsonSerializerOptions options) { + if (classInfo.ClassType == ClassType.KeyValuePair) + { + return classInfo.GetPolicyPropertyOfKeyValuePair(); + } + if (classInfo.ClassType != ClassType.Object) { return classInfo.GetPolicyProperty(); @@ -324,6 +334,22 @@ namespace System.Text.Json return propertyInfo; } + internal bool CreateRangeDelegatesContainsKey(string key) + { + return s_createRangeDelegates.ContainsKey(key); + } + + internal bool TryGetCreateRangeDelegate(string delegateKey, out object createRangeDelegate) + { + return s_createRangeDelegates.TryGetValue(delegateKey, out createRangeDelegate) && createRangeDelegate != null; + } + + internal bool TryAddCreateRangeDelegate(string key, object createRangeDelegate) + { + return s_createRangeDelegates.TryAdd(key, createRangeDelegate); + } + + private void VerifyMutable() { // The default options are hidden and thus should be immutable. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs index a52c246..b642b95 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs @@ -32,7 +32,9 @@ namespace System.Text.Json // Has an array or dictionary property been initialized. public bool PropertyInitialized; - // Support Immutable dictionary types. + // Support IDictionary constructible types, i.e. types that we + // support by passing and IDictionary to their constructors: + // immutable dictionaries, Hashtable, SortedList public IDictionary TempDictionaryValues; // For performance, we order the properties by the first deserialize and PropertyIndex helps find the right slot quicker. @@ -42,14 +44,17 @@ namespace System.Text.Json // The current JSON data for a property does not match a given POCO, so ignore the property (recursively). public bool Drain; - public bool IsImmutableDictionary => JsonClassInfo.ClassType == ClassType.ImmutableDictionary; + public bool IsIDictionaryConstructible => JsonClassInfo.ClassType == ClassType.IDictionaryConstructible; public bool IsDictionary => JsonClassInfo.ClassType == ClassType.Dictionary; + public bool IsKeyValuePair => JsonClassInfo.ClassType == ClassType.KeyValuePair; public bool IsDictionaryProperty => JsonPropertyInfo != null && !JsonPropertyInfo.IsPropertyPolicy && JsonPropertyInfo.ClassType == ClassType.Dictionary; - public bool IsImmutableDictionaryProperty => JsonPropertyInfo != null && - !JsonPropertyInfo.IsPropertyPolicy && (JsonPropertyInfo.ClassType == ClassType.ImmutableDictionary); + public bool IsIDictionaryConstructibleProperty => JsonPropertyInfo != null && + !JsonPropertyInfo.IsPropertyPolicy && (JsonPropertyInfo.ClassType == ClassType.IDictionaryConstructible); + public bool IsKeyValuePairProperty => JsonPropertyInfo != null && + !JsonPropertyInfo.IsPropertyPolicy && (JsonPropertyInfo.ClassType == ClassType.KeyValuePair); public bool IsEnumerable => JsonClassInfo.ClassType == ClassType.Enumerable; @@ -58,9 +63,12 @@ namespace System.Text.Json !JsonPropertyInfo.IsPropertyPolicy && JsonPropertyInfo.ClassType == ClassType.Enumerable; - public bool IsProcessingEnumerableOrDictionary => IsProcessingEnumerable || IsProcessingDictionary || IsProcessingImmutableDictionary; + public bool IsProcessingEnumerableOrDictionary => IsProcessingEnumerable || IsProcessingDictionary || IsProcessingIDictionaryConstructibleOrKeyValuePair; + public bool IsProcessingIDictionaryConstructibleOrKeyValuePair => IsProcessingIDictionaryConstructible || IsProcessingKeyValuePair; + public bool IsProcessingDictionary => IsDictionary || IsDictionaryProperty; - public bool IsProcessingImmutableDictionary => IsImmutableDictionary || IsImmutableDictionaryProperty; + public bool IsProcessingIDictionaryConstructible => IsIDictionaryConstructible || IsIDictionaryConstructibleProperty; + public bool IsProcessingKeyValuePair => IsKeyValuePair || IsKeyValuePairProperty; public bool IsProcessingEnumerable => IsEnumerable || IsEnumerableProperty; public bool IsProcessingValue @@ -73,6 +81,20 @@ namespace System.Text.Json return false; } + if (PropertyInitialized) + { + if ((IsEnumerable || IsDictionary || IsIDictionaryConstructible) && + JsonClassInfo.ElementClassInfo.ClassType == ClassType.Unknown) + { + return true; + } + else if ((IsEnumerableProperty || IsDictionaryProperty || IsIDictionaryConstructibleProperty) && + JsonPropertyInfo.ElementClassInfo.ClassType == ClassType.Unknown) + { + return true; + } + } + // We've got a property info. If we're a Value or polymorphic Value // (ClassType.Unknown), return true. ClassType type = JsonPropertyInfo.ClassType; @@ -80,8 +102,8 @@ namespace System.Text.Json KeyName != null && ( (IsDictionary && JsonClassInfo.ElementClassInfo.ClassType == ClassType.Unknown) || (IsDictionaryProperty && JsonPropertyInfo.ElementClassInfo.ClassType == ClassType.Unknown) || - (IsImmutableDictionary && JsonClassInfo.ElementClassInfo.ClassType == ClassType.Unknown) || - (IsImmutableDictionaryProperty && JsonPropertyInfo.ElementClassInfo.ClassType == ClassType.Unknown) + (IsIDictionaryConstructible && JsonClassInfo.ElementClassInfo.ClassType == ClassType.Unknown) || + (IsIDictionaryConstructibleProperty && JsonPropertyInfo.ElementClassInfo.ClassType == ClassType.Unknown) ); } } @@ -97,10 +119,14 @@ namespace System.Text.Json if (JsonClassInfo.ClassType == ClassType.Value || JsonClassInfo.ClassType == ClassType.Enumerable || JsonClassInfo.ClassType == ClassType.Dictionary || - JsonClassInfo.ClassType == ClassType.ImmutableDictionary) + JsonClassInfo.ClassType == ClassType.IDictionaryConstructible) { JsonPropertyInfo = JsonClassInfo.GetPolicyProperty(); } + else if (JsonClassInfo.ClassType == ClassType.KeyValuePair) + { + JsonPropertyInfo = JsonClassInfo.GetPolicyPropertyOfKeyValuePair(); + } } public void Reset() @@ -151,8 +177,8 @@ namespace System.Text.Json return null; } - Type propType = state.Current.JsonPropertyInfo.RuntimePropertyType; - if (typeof(IList).IsAssignableFrom(propType)) + Type propertyType = state.Current.JsonPropertyInfo.RuntimePropertyType; + if (typeof(IList).IsAssignableFrom(propertyType)) { // If IList, add the members as we create them. JsonClassInfo collectionClassInfo = state.Current.JsonPropertyInfo.RuntimeClassInfo; @@ -161,19 +187,19 @@ namespace System.Text.Json } else { - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(propType, reader, state.JsonPath); + ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(propertyType, reader, state.JsonPath); return null; } } public Type GetElementType() { - if (IsEnumerableProperty || IsDictionaryProperty || IsImmutableDictionaryProperty) + if (IsEnumerableProperty || IsDictionaryProperty || IsIDictionaryConstructibleProperty) { return JsonPropertyInfo.ElementClassInfo.Type; } - if (IsEnumerable || IsDictionary || IsImmutableDictionary) + if (IsEnumerable || IsDictionary || IsIDictionaryConstructible) { return JsonClassInfo.ElementClassInfo.Type; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMaterializer.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMaterializer.cs index 66abff3..065a1eb 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMaterializer.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMaterializer.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; -using System.Text.Json.Serialization.Converters; namespace System.Text.Json { @@ -60,7 +59,7 @@ namespace System.Text.Json } return createRange.CreateDelegate( - typeof(DefaultImmutableConverter.ImmutableCreateRangeDelegate<>).MakeGenericType(elementType), null); + typeof(JsonSerializerOptions.ImmutableCreateRangeDelegate<>).MakeGenericType(elementType), null); } public override object ImmutableDictionaryCreateRange(Type constructingType, Type elementType) @@ -73,7 +72,7 @@ namespace System.Text.Json } return createRange.CreateDelegate( - typeof(DefaultImmutableConverter.ImmutableDictCreateRangeDelegate<,>).MakeGenericType(typeof(string), elementType), null); + typeof(JsonSerializerOptions.ImmutableDictCreateRangeDelegate<,>).MakeGenericType(typeof(string), elementType), null); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMaterializer.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMaterializer.cs index 1750f5a..22e79dd 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMaterializer.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMaterializer.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.Reflection; -using System.Text.Json.Serialization.Converters; namespace System.Text.Json { @@ -33,7 +32,7 @@ namespace System.Text.Json } return createRange.CreateDelegate( - typeof(DefaultImmutableConverter.ImmutableCreateRangeDelegate<>).MakeGenericType(elementType), null); + typeof(JsonSerializerOptions.ImmutableCreateRangeDelegate<>).MakeGenericType(elementType), null); } public override object ImmutableDictionaryCreateRange(Type constructingType, Type elementType) @@ -46,7 +45,7 @@ namespace System.Text.Json } return createRange.CreateDelegate( - typeof(DefaultImmutableConverter.ImmutableDictCreateRangeDelegate<,>).MakeGenericType(typeof(string), elementType), null); + typeof(JsonSerializerOptions.ImmutableDictCreateRangeDelegate<,>).MakeGenericType(typeof(string), elementType), null); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs index 9f7dd4d..95b2df1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs @@ -52,16 +52,16 @@ namespace System.Text.Json Current.PopStackOnEnd = true; Current.JsonPropertyInfo = Current.JsonClassInfo.GetPolicyProperty(); } - else if (classType == ClassType.ImmutableDictionary) + else if (classType == ClassType.IDictionaryConstructible) { Current.PopStackOnEnd = true; Current.JsonPropertyInfo = Current.JsonClassInfo.GetPolicyProperty(); - Current.IsImmutableDictionary = true; + Current.IsIDictionaryConstructible = true; } else { - Debug.Assert(nextClassInfo.ClassType == ClassType.Object || nextClassInfo.ClassType == ClassType.Unknown); + Debug.Assert(nextClassInfo.ClassType == ClassType.Object || nextClassInfo.ClassType == ClassType.KeyValuePair || nextClassInfo.ClassType == ClassType.Unknown); Current.PopStackOnEndObject = true; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs index 2526401..e7e1e50 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs @@ -2,10 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Buffers; using System.Collections; using System.Diagnostics; -using System.Text.Json.Serialization.Converters; namespace System.Text.Json { @@ -18,9 +16,9 @@ namespace System.Text.Json // Support Dictionary keys. public string KeyName; - // Whether the current object is an immutable dictionary. - public bool IsImmutableDictionary; - public bool IsImmutableDictionaryProperty; + // Whether the current object can be constructed with IDictionary. + public bool IsIDictionaryConstructible; + public bool IsIDictionaryConstructibleProperty; // The current enumerator for the IEnumerable or IDictionary. public IEnumerator Enumerator; @@ -47,10 +45,17 @@ namespace System.Text.Json { JsonPropertyInfo = JsonClassInfo.GetPolicyProperty(); } - else if (JsonClassInfo.ClassType == ClassType.ImmutableDictionary) + else if (JsonClassInfo.ClassType == ClassType.IDictionaryConstructible) { JsonPropertyInfo = JsonClassInfo.GetPolicyProperty(); - IsImmutableDictionary = true; + IsIDictionaryConstructible = true; + } + else if (JsonClassInfo.ClassType == ClassType.KeyValuePair) + { + JsonPropertyInfo = JsonClassInfo.GetPolicyPropertyOfKeyValuePair(); + // Advance to the next property, since the first one is the KeyValuePair type itself, + // not its first property (Key or Value). + PropertyIndex++; } } @@ -70,7 +75,7 @@ namespace System.Text.Json Debug.Assert(writeNull == false); // Write start without a property name. - if (classType == ClassType.Object || classType == ClassType.Dictionary || classType == ClassType.ImmutableDictionary) + if (classType == ClassType.Object || classType == ClassType.Dictionary || classType == ClassType.IDictionaryConstructible) { writer.WriteStartObject(); StartObjectWritten = true; @@ -91,7 +96,7 @@ namespace System.Text.Json } else if (classType == ClassType.Object || classType == ClassType.Dictionary || - classType == ClassType.ImmutableDictionary) + classType == ClassType.IDictionaryConstructible) { writer.WriteStartObject(propertyName); StartObjectWritten = true; @@ -111,7 +116,7 @@ namespace System.Text.Json JsonClassInfo = null; JsonPropertyInfo = null; PropertyIndex = 0; - IsImmutableDictionary = false; + IsIDictionaryConstructible = false; PopStackOnEndObject = false; PopStackOnEnd = false; StartObjectWritten = false; @@ -121,7 +126,7 @@ namespace System.Text.Json { PropertyIndex = 0; PopStackOnEndObject = false; - IsImmutableDictionaryProperty = false; + IsIDictionaryConstructibleProperty = false; JsonPropertyInfo = null; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs index bd505b5..4e8b1fe 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs @@ -42,6 +42,13 @@ namespace System.Text.Json } [MethodImpl(MethodImplOptions.NoInlining)] + public static JsonException GetJsonException_DeserializeUnableToConvertValue(Type propertyType, string path) + { + string message = SR.Format(SR.DeserializeUnableToConvertValue, propertyType) + $" Path: {path}."; + return new JsonException(message, path, null, null); + } + + [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowJsonException_DeserializeCannotBeNull(in Utf8JsonReader reader, string path) { ThowJsonException(SR.DeserializeCannotBeNull, in reader, path); diff --git a/src/libraries/System.Text.Json/tests/Serialization/Array.ReadTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Array.ReadTests.cs index aeddfbb..d19318f 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/Array.ReadTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/Array.ReadTests.cs @@ -268,6 +268,27 @@ namespace System.Text.Json.Serialization.Tests } [Fact] + public static void ReadClassWithObjectIEnumerable() + { + TestClassWithObjectIEnumerable obj = JsonSerializer.Parse(TestClassWithObjectIEnumerable.s_data); + obj.Verify(); + } + + [Fact] + public static void ReadClassWithObjectIList() + { + TestClassWithObjectIList obj = JsonSerializer.Parse(TestClassWithObjectIList.s_data); + obj.Verify(); + } + + [Fact] + public static void ReadClassWithObjectICollection() + { + TestClassWithObjectICollection obj = JsonSerializer.Parse(TestClassWithObjectICollection.s_data); + obj.Verify(); + } + + [Fact] public static void ReadClassWithObjectIEnumerableT() { TestClassWithObjectIEnumerableT obj = JsonSerializer.Parse(TestClassWithObjectIEnumerableT.s_data); @@ -303,6 +324,32 @@ namespace System.Text.Json.Serialization.Tests } [Fact] + public static void ReadClassWithGenericIEnumerable() + { + TestClassWithGenericIEnumerable obj = JsonSerializer.Parse(TestClassWithGenericIEnumerable.s_data); + obj.Verify(); + } + + [Fact] + public static void ReadClassWithGenericIList() + { + TestClassWithGenericIList obj = JsonSerializer.Parse(TestClassWithGenericIList.s_data); + obj.Verify(); + } + + [Fact] + public static void ReadClassWithGenericICollection() + { + TestClassWithGenericICollection obj = JsonSerializer.Parse(TestClassWithGenericICollection.s_data); + } + + public static void ReadClassWithObjectISetT() + { + TestClassWithObjectISetT obj = JsonSerializer.Parse(TestClassWithObjectISetT.s_data); + obj.Verify(); + } + + [Fact] public static void ReadClassWithGenericIEnumerableT() { TestClassWithGenericIEnumerableT obj = JsonSerializer.Parse(TestClassWithGenericIEnumerableT.s_data); @@ -338,6 +385,13 @@ namespace System.Text.Json.Serialization.Tests } [Fact] + public static void ReadClassWithGenericISetT() + { + TestClassWithGenericISetT obj = JsonSerializer.Parse(TestClassWithGenericISetT.s_data); + obj.Verify(); + } + + [Fact] public static void ReadClassWithObjectIEnumerableConstructibleTypes() { TestClassWithObjectIEnumerableConstructibleTypes obj = JsonSerializer.Parse(TestClassWithObjectIEnumerableConstructibleTypes.s_data); diff --git a/src/libraries/System.Text.Json/tests/Serialization/DictionaryTests.cs b/src/libraries/System.Text.Json/tests/Serialization/DictionaryTests.cs index fbe5a18..544c24b 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/DictionaryTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/DictionaryTests.cs @@ -18,6 +18,18 @@ namespace System.Text.Json.Serialization.Tests const string ReorderedJsonString = @"{""Hello2"":""World2"",""Hello"":""World""}"; { + IDictionary obj = JsonSerializer.Parse(JsonString); + Assert.Equal("World", ((JsonElement)obj["Hello"]).GetString()); + Assert.Equal("World2", ((JsonElement)obj["Hello2"]).GetString()); + + string json = JsonSerializer.ToString(obj); + Assert.Equal(JsonString, json); + + json = JsonSerializer.ToString(obj); + Assert.Equal(JsonString, json); + } + + { Dictionary obj = JsonSerializer.Parse>(JsonString); Assert.Equal("World", obj["Hello"]); Assert.Equal("World2", obj["Hello2"]); @@ -30,6 +42,18 @@ namespace System.Text.Json.Serialization.Tests } { + SortedDictionary obj = JsonSerializer.Parse>(JsonString); + Assert.Equal("World", obj["Hello"]); + Assert.Equal("World2", obj["Hello2"]); + + string json = JsonSerializer.ToString(obj); + Assert.Equal(JsonString, json); + + json = JsonSerializer.ToString(obj); + Assert.Equal(JsonString, json); + } + + { IDictionary obj = JsonSerializer.Parse>(JsonString); Assert.Equal("World", obj["Hello"]); Assert.Equal("World2", obj["Hello2"]); @@ -88,11 +112,39 @@ namespace System.Text.Json.Serialization.Tests json = JsonSerializer.ToString(obj); Assert.True(JsonString == json); } + + { + Hashtable obj = JsonSerializer.Parse(JsonString); + Assert.Equal("World", ((JsonElement)obj["Hello"]).GetString()); + Assert.Equal("World2", ((JsonElement)obj["Hello2"]).GetString()); + + string json = JsonSerializer.ToString(obj); + Assert.True(JsonString == json || ReorderedJsonString == json); + + json = JsonSerializer.ToString(obj); + Assert.True(JsonString == json || ReorderedJsonString == json); + } + + { + SortedList obj = JsonSerializer.Parse(JsonString); + Assert.Equal("World", ((JsonElement)obj["Hello"]).GetString()); + Assert.Equal("World2", ((JsonElement)obj["Hello2"]).GetString()); + + string json = JsonSerializer.ToString(obj); + Assert.Equal(JsonString, json); + + json = JsonSerializer.ToString(obj); + Assert.Equal(JsonString, json); + } } [Fact] public static void DuplicateKeysFail() { + // Non-generic IDictionary case. + Assert.Throws(() => JsonSerializer.Parse( + @"{""Hello"":""World"", ""Hello"":""World""}")); + // Strongly-typed IDictionary<,> case. Assert.Throws(() => JsonSerializer.Parse>( @"{""Hello"":""World"", ""Hello"":""World""}")); @@ -105,14 +157,27 @@ namespace System.Text.Json.Serialization.Tests [Fact] public static void DictionaryOfObject() { - Dictionary obj = JsonSerializer.Parse>(@"{""Key1"":1}"); - Assert.Equal(1, obj.Count); - JsonElement element = (JsonElement)obj["Key1"]; - Assert.Equal(JsonValueType.Number, element.Type); - Assert.Equal(1, element.GetInt32()); + { + IDictionary obj = JsonSerializer.Parse(@"{""Key1"":1}"); + Assert.Equal(1, obj.Count); + JsonElement element = (JsonElement)obj["Key1"]; + Assert.Equal(JsonValueType.Number, element.Type); + Assert.Equal(1, element.GetInt32()); - string json = JsonSerializer.ToString(obj); - Assert.Equal(@"{""Key1"":1}", json); + string json = JsonSerializer.ToString(obj); + Assert.Equal(@"{""Key1"":1}", json); + } + + { + Dictionary obj = JsonSerializer.Parse>(@"{""Key1"":1}"); + Assert.Equal(1, obj.Count); + JsonElement element = (JsonElement)obj["Key1"]; + Assert.Equal(JsonValueType.Number, element.Type); + Assert.Equal(1, element.GetInt32()); + + string json = JsonSerializer.ToString(obj); + Assert.Equal(@"{""Key1"":1}", json); + } } [Theory] @@ -200,6 +265,29 @@ namespace System.Text.Json.Serialization.Tests const string JsonString = @"{""Key1"":[1,2],""Key2"":[3,4]}"; { + IDictionary obj = JsonSerializer.Parse(JsonString); + + Assert.Equal(2, obj.Count); + + int expectedNumber = 1; + + JsonElement element = (JsonElement)obj["Key1"]; + foreach (JsonElement value in element.EnumerateArray()) + { + Assert.Equal(expectedNumber++, value.GetInt32()); + } + + element = (JsonElement)obj["Key2"]; + foreach (JsonElement value in element.EnumerateArray()) + { + Assert.Equal(expectedNumber++, value.GetInt32()); + } + + string json = JsonSerializer.ToString(obj); + Assert.Equal(JsonString, json); + } + + { IDictionary> obj = JsonSerializer.Parse>>(JsonString); Assert.Equal(2, obj.Count); @@ -461,6 +549,63 @@ namespace System.Text.Json.Serialization.Tests public static void DictionaryOfClasses() { { + IDictionary obj; + + { + string json = @"{""Key1"":" + SimpleTestClass.s_json + @",""Key2"":" + SimpleTestClass.s_json + "}"; + obj = JsonSerializer.Parse(json); + Assert.Equal(2, obj.Count); + + if (obj["Key1"] is JsonElement element) + { + SimpleTestClass result = JsonSerializer.Parse(element.GetRawText()); + result.Verify(); + } + else + { + ((SimpleTestClass)obj["Key1"]).Verify(); + ((SimpleTestClass)obj["Key2"]).Verify(); + } + } + + { + // We can't compare against the json string above because property ordering is not deterministic (based on reflection order) + // so just round-trip the json and compare. + string json = JsonSerializer.ToString(obj); + obj = JsonSerializer.Parse(json); + Assert.Equal(2, obj.Count); + + if (obj["Key1"] is JsonElement element) + { + SimpleTestClass result = JsonSerializer.Parse(element.GetRawText()); + result.Verify(); + } + else + { + ((SimpleTestClass)obj["Key1"]).Verify(); + ((SimpleTestClass)obj["Key2"]).Verify(); + } + } + + { + string json = JsonSerializer.ToString(obj); + obj = JsonSerializer.Parse(json); + Assert.Equal(2, obj.Count); + + if (obj["Key1"] is JsonElement element) + { + SimpleTestClass result = JsonSerializer.Parse(element.GetRawText()); + result.Verify(); + } + else + { + ((SimpleTestClass)obj["Key1"]).Verify(); + ((SimpleTestClass)obj["Key2"]).Verify(); + } + } + } + + { Dictionary obj; { @@ -601,30 +746,6 @@ namespace System.Text.Json.Serialization.Tests public static void HashtableFail() { { - string json = @"{""Key"":""Value""}"; - - // Verify we can deserialize into Dictionary<,> - JsonSerializer.Parse>(json); - - // We don't support non-generic IDictionary - Assert.Throws(() => JsonSerializer.Parse(json)); - } - - { - Hashtable ht = new Hashtable(); - ht.Add("Key", "Value"); - - Assert.Throws(() => JsonSerializer.ToString(ht)); - } - - { - string json = @"{""Key"":""Value""}"; - - // We don't support non-generic IDictionary - Assert.Throws(() => JsonSerializer.Parse(json)); - } - - { IDictionary ht = new Hashtable(); ht.Add("Key", "Value"); Assert.Throws(() => JsonSerializer.ToString(ht)); diff --git a/src/libraries/System.Text.Json/tests/Serialization/Object.ReadTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Object.ReadTests.cs index ed3696e..3913736 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/Object.ReadTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/Object.ReadTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; using Xunit; namespace System.Text.Json.Serialization.Tests diff --git a/src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs b/src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs index 086e7cf..7b35350 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using Xunit; @@ -95,39 +96,67 @@ namespace System.Text.Json.Serialization.Tests json = JsonSerializer.ToString(list); Assert.Equal(ExpectedJson, json); - IEnumerable ienumerable = new List { 1, true, address, null, "foo" }; + IEnumerable ienumerable = new List { 1, true, address, null, "foo" }; json = JsonSerializer.ToString(ienumerable); Assert.Equal(ExpectedJson, json); json = JsonSerializer.ToString(ienumerable); Assert.Equal(ExpectedJson, json); - IList ilist = new List { 1, true, address, null, "foo" }; + IList ilist = new List { 1, true, address, null, "foo" }; json = JsonSerializer.ToString(ilist); Assert.Equal(ExpectedJson, json); json = JsonSerializer.ToString(ilist); Assert.Equal(ExpectedJson, json); - ICollection icollection = new List { 1, true, address, null, "foo" }; + ICollection icollection = new List { 1, true, address, null, "foo" }; json = JsonSerializer.ToString(icollection); Assert.Equal(ExpectedJson, json); json = JsonSerializer.ToString(icollection); Assert.Equal(ExpectedJson, json); - IReadOnlyCollection ireadonlycollection = new List { 1, true, address, null, "foo" }; - json = JsonSerializer.ToString(ireadonlycollection); + IEnumerable genericIEnumerable = new List { 1, true, address, null, "foo" }; + json = JsonSerializer.ToString(genericIEnumerable); Assert.Equal(ExpectedJson, json); - json = JsonSerializer.ToString(ireadonlycollection); + json = JsonSerializer.ToString(genericIEnumerable); Assert.Equal(ExpectedJson, json); - IReadOnlyList ireadonlylist = new List { 1, true, address, null, "foo" }; - json = JsonSerializer.ToString(ireadonlylist); + IList genericIList = new List { 1, true, address, null, "foo" }; + json = JsonSerializer.ToString(genericIList); Assert.Equal(ExpectedJson, json); - json = JsonSerializer.ToString(ireadonlylist); + json = JsonSerializer.ToString(genericIList); + Assert.Equal(ExpectedJson, json); + + ICollection genericICollection = new List { 1, true, address, null, "foo" }; + json = JsonSerializer.ToString(genericICollection); + Assert.Equal(ExpectedJson, json); + + json = JsonSerializer.ToString(genericICollection); + Assert.Equal(ExpectedJson, json); + + IReadOnlyCollection genericIReadOnlyCollection = new List { 1, true, address, null, "foo" }; + json = JsonSerializer.ToString(genericIReadOnlyCollection); + Assert.Equal(ExpectedJson, json); + + json = JsonSerializer.ToString(genericIReadOnlyCollection); + Assert.Equal(ExpectedJson, json); + + IReadOnlyList genericIReadonlyList = new List { 1, true, address, null, "foo" }; + json = JsonSerializer.ToString(genericIReadonlyList); + Assert.Equal(ExpectedJson, json); + + json = JsonSerializer.ToString(genericIReadonlyList); + Assert.Equal(ExpectedJson, json); + + ISet iset = new HashSet { 1, true, address, null, "foo" }; + json = JsonSerializer.ToString(iset); + Assert.Equal(ExpectedJson, json); + + json = JsonSerializer.ToString(iset); Assert.Equal(ExpectedJson, json); Stack stack = new Stack(new List { 1, true, address, null, "foo" }); @@ -259,11 +288,15 @@ namespace System.Text.Json.Serialization.Tests Assert.Contains(@"""Address"":{""City"":""MyCity""}", json); Assert.Contains(@"""List"":[""Hello"",""World""]", json); Assert.Contains(@"""Array"":[""Hello"",""Again""]", json); + Assert.Contains(@"""IEnumerable"":[""Hello"",""World""]", json); + Assert.Contains(@"""IList"":[""Hello"",""World""]", json); + Assert.Contains(@"""ICollection"":[""Hello"",""World""]", json); Assert.Contains(@"""IEnumerableT"":[""Hello"",""World""]", json); Assert.Contains(@"""IListT"":[""Hello"",""World""]", json); Assert.Contains(@"""ICollectionT"":[""Hello"",""World""]", json); Assert.Contains(@"""IReadOnlyCollectionT"":[""Hello"",""World""]", json); Assert.Contains(@"""IReadOnlyListT"":[""Hello"",""World""]", json); + Assert.Contains(@"""ISetT"":[""Hello"",""World""]", json); Assert.Contains(@"""StackT"":[""World"",""Hello""]", json); Assert.Contains(@"""QueueT"":[""Hello"",""World""]", json); Assert.Contains(@"""HashSetT"":[""Hello"",""World""]", json); diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.Polymorphic.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.Polymorphic.cs index 23708f6..7578ecc 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.Polymorphic.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.Polymorphic.cs @@ -111,11 +111,15 @@ namespace System.Text.Json.Serialization.Tests public object /*Address*/ Address { get; set; } public object /*List*/ List { get; set; } public object /*string[]*/ Array { get; set; } + public object /*IEnumerable of strings*/ IEnumerable { get; set; } + public object /*IList of strings */ IList { get; set; } + public object /*ICollection of strings */ ICollection { get; set; } public object /*IEnumerable*/ IEnumerableT { get; set; } public object /*IList*/ IListT { get; set; } public object /*ICollection*/ ICollectionT { get; set; } public object /*IReadOnlyCollection*/ IReadOnlyCollectionT { get; set; } public object /*IReadOnlyList*/ IReadOnlyListT { get; set; } + public object /*ISet*/ ISetT { get; set; } public object /*Stack*/ StackT { get; set; } public object /*Queue*/ QueueT { get; set; } public object /*HashSet*/ HashSetT { get; set; } @@ -141,11 +145,15 @@ namespace System.Text.Json.Serialization.Tests List = new List { "Hello", "World" }; Array = new string[] { "Hello", "Again" }; + IEnumerable = new List { "Hello", "World" }; + IList = new List { "Hello", "World" }; + ICollection = new List { "Hello", "World" }; IEnumerableT = new List { "Hello", "World" }; IListT = new List { "Hello", "World" }; ICollectionT = new List { "Hello", "World" }; IReadOnlyCollectionT = new List { "Hello", "World" }; IReadOnlyListT = new List { "Hello", "World" }; + ISetT = new HashSet { "Hello", "World" }; StackT = new Stack(new List { "Hello", "World" }); QueueT = new Queue(new List { "Hello", "World" }); HashSetT = new HashSet(new List { "Hello", "World" }); diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClass.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClass.cs index 8049d12..d0367ef 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClass.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClass.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -59,14 +60,20 @@ namespace System.Text.Json.Serialization.Tests public int[][][] MyInt16ThreeDimensionArray { get; set; } public List>> MyInt16ThreeDimensionList { get; set; } public List MyStringList { get; set; } + public IEnumerable MyStringIEnumerable { get; set; } + public IList MyStringIList { get; set; } + public ICollection MyStringICollection { get; set; } public IEnumerable MyStringIEnumerableT { get; set; } public IList MyStringIListT { get; set; } public ICollection MyStringICollectionT { get; set; } public IReadOnlyCollection MyStringIReadOnlyCollectionT { get; set; } public IReadOnlyList MyStringIReadOnlyListT { get; set; } - public Dictionary MyStringToStringDict { get; set; } - public IDictionary MyStringToStringIDict { get; set; } - public IReadOnlyDictionary MyStringToStringIReadOnlyDict { get; set; } + public ISet MyStringISetT { get; set; } + public KeyValuePair MyStringToStringKeyValuePair { get; set; } + public IDictionary MyStringToStringIDict { get; set; } + public Dictionary MyStringToStringGenericDict { get; set; } + public IDictionary MyStringToStringGenericIDict { get; set; } + public IReadOnlyDictionary MyStringToStringGenericIReadOnlyDict { get; set; } public ImmutableDictionary MyStringToStringImmutableDict { get; set; } public IImmutableDictionary MyStringToStringIImmutableDict { get; set; } public ImmutableSortedDictionary MyStringToStringImmutableSortedDict { get; set; } @@ -108,9 +115,11 @@ namespace System.Text.Json.Serialization.Tests @"""MyDateTime"" : ""2019-01-30T12:01:02.0000000Z""," + @"""MyDateTimeOffset"" : ""2019-01-30T12:01:02.0000000+01:00""," + @"""MyEnum"" : 2," + // int by default - @"""MyStringToStringDict"" : {""key"" : ""value""}," + + @"""MyStringToStringKeyValuePair"" : {""Key"" : ""myKey"", ""Value"" : ""myValue""}," + @"""MyStringToStringIDict"" : {""key"" : ""value""}," + - @"""MyStringToStringIReadOnlyDict"" : {""key"" : ""value""}," + + @"""MyStringToStringGenericDict"" : {""key"" : ""value""}," + + @"""MyStringToStringGenericIDict"" : {""key"" : ""value""}," + + @"""MyStringToStringGenericIReadOnlyDict"" : {""key"" : ""value""}," + @"""MyStringToStringImmutableDict"" : {""key"" : ""value""}," + @"""MyStringToStringIImmutableDict"" : {""key"" : ""value""}," + @"""MyStringToStringImmutableSortedDict"" : {""key"" : ""value""}"; @@ -139,11 +148,15 @@ namespace System.Text.Json.Serialization.Tests @"""MyInt16ThreeDimensionArray"" : [[[11, 12],[13, 14]],[[21,22],[23,24]]]," + @"""MyInt16ThreeDimensionList"" : [[[11, 12],[13, 14]],[[21,22],[23,24]]]," + @"""MyStringList"" : [""Hello""]," + + @"""MyStringIEnumerable"" : [""Hello""]," + + @"""MyStringIList"" : [""Hello""]," + + @"""MyStringICollection"" : [""Hello""]," + @"""MyStringIEnumerableT"" : [""Hello""]," + @"""MyStringIListT"" : [""Hello""]," + @"""MyStringICollectionT"" : [""Hello""]," + @"""MyStringIReadOnlyCollectionT"" : [""Hello""]," + @"""MyStringIReadOnlyListT"" : [""Hello""]," + + @"""MyStringISetT"" : [""Hello""]," + @"""MyStringStackT"" : [""Hello"", ""World""]," + @"""MyStringQueueT"" : [""Hello"", ""World""]," + @"""MyStringHashSetT"" : [""Hello""]," + @@ -229,19 +242,28 @@ namespace System.Text.Json.Serialization.Tests list2.Add(new List { 23, 24 }); MyStringList = new List() { "Hello" }; + + MyStringIEnumerable = new string[] { "Hello" }; + MyStringIList = new string[] { "Hello" }; + MyStringICollection = new string[] { "Hello" }; + MyStringIEnumerableT = new string[] { "Hello" }; MyStringIListT = new string[] { "Hello" }; MyStringICollectionT = new string[] { "Hello" }; MyStringIReadOnlyCollectionT = new string[] { "Hello" }; MyStringIReadOnlyListT = new string[] { "Hello" }; + MyStringISetT = new HashSet { "Hello" }; - MyStringToStringDict = new Dictionary { { "key", "value" } }; + MyStringToStringKeyValuePair = new KeyValuePair("myKey", "myValue"); MyStringToStringIDict = new Dictionary { { "key", "value" } }; - MyStringToStringIReadOnlyDict = new Dictionary { { "key", "value" } }; - MyStringToStringImmutableDict = ImmutableDictionary.CreateRange(MyStringToStringDict); - MyStringToStringIImmutableDict = ImmutableDictionary.CreateRange(MyStringToStringDict); - MyStringToStringImmutableSortedDict = ImmutableSortedDictionary.CreateRange(MyStringToStringDict); + MyStringToStringGenericDict = new Dictionary { { "key", "value" } }; + MyStringToStringGenericIDict = new Dictionary { { "key", "value" } }; + MyStringToStringGenericIReadOnlyDict = new Dictionary { { "key", "value" } }; + + MyStringToStringImmutableDict = ImmutableDictionary.CreateRange(MyStringToStringGenericDict); + MyStringToStringIImmutableDict = ImmutableDictionary.CreateRange(MyStringToStringGenericDict); + MyStringToStringImmutableSortedDict = ImmutableSortedDictionary.CreateRange(MyStringToStringGenericDict); MyStringStackT = new Stack(new List() { "Hello", "World" } ); MyStringQueueT = new Queue(new List() { "Hello", "World" }); @@ -331,20 +353,99 @@ namespace System.Text.Json.Serialization.Tests Assert.Equal(24, MyInt16ThreeDimensionList[1][1][1]); Assert.Equal("Hello", MyStringList[0]); + + IEnumerator enumerator = MyStringIEnumerable.GetEnumerator(); + enumerator.MoveNext(); + { + // Verifying after deserialization. + if (enumerator.Current is JsonElement currentJsonElement) + { + Assert.Equal("Hello", currentJsonElement.GetString()); + } + // Verifying test data. + else + { + Assert.Equal("Hello", enumerator.Current); + } + } + + { + // Verifying after deserialization. + if (MyStringIList[0] is JsonElement currentJsonElement) + { + Assert.Equal("Hello", currentJsonElement.GetString()); + } + // Verifying test data. + else + { + Assert.Equal("Hello", enumerator.Current); + } + } + + enumerator = MyStringICollection.GetEnumerator(); + enumerator.MoveNext(); + { + // Verifying after deserialization. + if (enumerator.Current is JsonElement currentJsonElement) + { + Assert.Equal("Hello", currentJsonElement.GetString()); + } + // Verifying test data. + else + { + Assert.Equal("Hello", enumerator.Current); + } + } + Assert.Equal("Hello", MyStringIEnumerableT.First()); Assert.Equal("Hello", MyStringIListT[0]); Assert.Equal("Hello", MyStringICollectionT.First()); Assert.Equal("Hello", MyStringIReadOnlyCollectionT.First()); Assert.Equal("Hello", MyStringIReadOnlyListT[0]); + Assert.Equal("Hello", MyStringISetT.First()); - Assert.Equal("value", MyStringToStringDict["key"]); - Assert.Equal("value", MyStringToStringIDict["key"]); - Assert.Equal("value", MyStringToStringIReadOnlyDict["key"]); + enumerator = MyStringToStringIDict.GetEnumerator(); + enumerator.MoveNext(); + { + // Verifying after deserialization. + if (enumerator.Current is JsonElement currentJsonElement) + { + IEnumerator jsonEnumerator = currentJsonElement.EnumerateObject(); + jsonEnumerator.MoveNext(); + + JsonProperty property = (JsonProperty)jsonEnumerator.Current; + + Assert.Equal("key", property.Name); + Assert.Equal("value", property.Value.GetString()); + } + // Verifying test data. + else + { + DictionaryEntry entry = (DictionaryEntry)enumerator.Current; + Assert.Equal("key", entry.Key); + + if (entry.Value is JsonElement element) + { + Assert.Equal("value", element.GetString()); + } + else + { + Assert.Equal("value", entry.Value); + } + } + } + + Assert.Equal("value", MyStringToStringGenericDict["key"]); + Assert.Equal("value", MyStringToStringGenericIDict["key"]); + Assert.Equal("value", MyStringToStringGenericIReadOnlyDict["key"]); Assert.Equal("value", MyStringToStringImmutableDict["key"]); Assert.Equal("value", MyStringToStringIImmutableDict["key"]); Assert.Equal("value", MyStringToStringImmutableSortedDict["key"]); + Assert.Equal("myKey", MyStringToStringKeyValuePair.Key); + Assert.Equal("myValue", MyStringToStringKeyValuePair.Value); + Assert.Equal(2, MyStringStackT.Count); Assert.True(MyStringStackT.Contains("Hello")); Assert.True(MyStringStackT.Contains("World")); diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs index 1417945..dc21f4f 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -29,14 +30,20 @@ namespace System.Text.Json.Serialization.Tests public object MyDateTimeArray { get; set; } public object MyEnumArray { get; set; } public object MyStringList { get; set; } + public object MyStringIEnumerable { get; set; } + public object MyStringIList { get; set; } + public object MyStringICollection { get; set; } public object MyStringIEnumerableT { get; set; } public object MyStringIListT { get; set; } public object MyStringICollectionT { get; set; } public object MyStringIReadOnlyCollectionT { get; set; } public object MyStringIReadOnlyListT { get; set; } - public object MyStringToStringDict { get; set; } + public object MyStringISetT { get; set; } + public object MyStringToStringKeyValuePair { get; set; } public object MyStringToStringIDict { get; set; } - public object MyStringToStringIReadOnlyDict { get; set; } + public object MyStringToStringGenericDict { get; set; } + public object MyStringToStringGenericIDict { get; set; } + public object MyStringToStringGenericIReadOnlyDict { get; set; } public object MyStringToStringImmutableDict { get; set; } public object MyStringToStringIImmutableDict { get; set; } public object MyStringToStringImmutableSortedDict { get; set; } @@ -92,14 +99,20 @@ namespace System.Text.Json.Serialization.Tests @"""MyDateTimeArray"" : [""2019-01-30T12:01:02.0000000Z""]," + @"""MyEnumArray"" : [2]," + // int by default @"""MyStringList"" : [""Hello""]," + + @"""MyStringIEnumerable"" : [""Hello""]," + + @"""MyStringIList"" : [""Hello""]," + + @"""MyStringICollection"" : [""Hello""]," + @"""MyStringIEnumerableT"" : [""Hello""]," + @"""MyStringIListT"" : [""Hello""]," + @"""MyStringICollectionT"" : [""Hello""]," + @"""MyStringIReadOnlyCollectionT"" : [""Hello""]," + @"""MyStringIReadOnlyListT"" : [""Hello""]," + - @"""MyStringToStringDict"" : {""key"" : ""value""}," + + @"""MyStringISetT"" : [""Hello""]," + + @"""MyStringToStringKeyValuePair"" : {""Key"" : ""myKey"", ""Value"" : ""myValue""}," + @"""MyStringToStringIDict"" : {""key"" : ""value""}," + - @"""MyStringToStringIReadOnlyDict"" : {""key"" : ""value""}," + + @"""MyStringToStringGenericDict"" : {""key"" : ""value""}," + + @"""MyStringToStringGenericIDict"" : {""key"" : ""value""}," + + @"""MyStringToStringGenericIReadOnlyDict"" : {""key"" : ""value""}," + @"""MyStringToStringImmutableDict"" : {""key"" : ""value""}," + @"""MyStringToStringIImmutableDict"" : {""key"" : ""value""}," + @"""MyStringToStringImmutableSortedDict"" : {""key"" : ""value""}," + @@ -144,19 +157,30 @@ namespace System.Text.Json.Serialization.Tests MyEnumArray = new SampleEnum[] { SampleEnum.Two }; MyStringList = new List() { "Hello" }; + + MyStringIEnumerable = new string[] { "Hello" }; + MyStringIList = new string[] { "Hello" }; + MyStringICollection = new string[] { "Hello" }; + MyStringIEnumerableT = new string[] { "Hello" }; MyStringIListT = new string[] { "Hello" }; MyStringICollectionT = new string[] { "Hello" }; MyStringIReadOnlyCollectionT = new string[] { "Hello" }; MyStringIReadOnlyListT = new string[] { "Hello" }; + MyStringIReadOnlyListT = new HashSet { "Hello" }; + + MyStringISetT = new HashSet { "Hello" }; - MyStringToStringDict = new Dictionary { { "key", "value" } }; + MyStringToStringKeyValuePair = new KeyValuePair("myKey", "myValue"); MyStringToStringIDict = new Dictionary { { "key", "value" } }; - MyStringToStringIReadOnlyDict = new Dictionary { { "key", "value" } }; - MyStringToStringImmutableDict = ImmutableDictionary.CreateRange((Dictionary)MyStringToStringDict); - MyStringToStringIImmutableDict = ImmutableDictionary.CreateRange((Dictionary)MyStringToStringDict); - MyStringToStringImmutableSortedDict = ImmutableSortedDictionary.CreateRange((Dictionary)MyStringToStringDict); + MyStringToStringGenericDict = new Dictionary { { "key", "value" } }; + MyStringToStringGenericIDict = new Dictionary { { "key", "value" } }; + MyStringToStringGenericIReadOnlyDict = new Dictionary { { "key", "value" } }; + + MyStringToStringImmutableDict = ImmutableDictionary.CreateRange((Dictionary)MyStringToStringGenericDict); + MyStringToStringIImmutableDict = ImmutableDictionary.CreateRange((Dictionary)MyStringToStringGenericDict); + MyStringToStringImmutableSortedDict = ImmutableSortedDictionary.CreateRange((Dictionary)MyStringToStringGenericDict); MyStringStackT = new Stack(new List() { "Hello", "World" }); MyStringQueueT = new Queue(new List() { "Hello", "World" }); @@ -198,20 +222,41 @@ namespace System.Text.Json.Serialization.Tests Assert.Equal(SampleEnum.Two, ((SampleEnum[])MyEnumArray)[0]); Assert.Equal("Hello", ((List)MyStringList)[0]); + + ((IEnumerable)MyStringIEnumerable).GetEnumerator().MoveNext(); + Assert.Equal("Hello", (string)((IEnumerable)MyStringIEnumerable).GetEnumerator().Current); + + Assert.Equal("Hello", (string)((IList)MyStringIList)[0]); + + ((ICollection)MyStringICollection).GetEnumerator().MoveNext(); + Assert.Equal("Hello", (string)((ICollection)MyStringICollection).GetEnumerator().Current); + Assert.Equal("Hello", ((IEnumerable)MyStringIEnumerableT).First()); Assert.Equal("Hello", ((IList)MyStringIListT)[0]); Assert.Equal("Hello", ((ICollection)MyStringICollectionT).First()); Assert.Equal("Hello", ((IReadOnlyCollection)MyStringIReadOnlyCollectionT).First()); Assert.Equal("Hello", ((IReadOnlyList)MyStringIReadOnlyListT)[0]); + Assert.Equal("Hello", ((ISet)MyStringISetT).First()); + - Assert.Equal("value", ((Dictionary)MyStringToStringDict)["key"]); - Assert.Equal("value", ((IDictionary)MyStringToStringIDict)["key"]); - Assert.Equal("value", ((IReadOnlyDictionary)MyStringToStringIReadOnlyDict)["key"]); + IEnumerator enumerator = ((IDictionary)MyStringToStringIDict).GetEnumerator(); + enumerator.MoveNext(); + + DictionaryEntry entry = (DictionaryEntry)(enumerator.Current); + Assert.Equal("key", entry.Key); + Assert.Equal("value", entry.Value); + + Assert.Equal("value", ((Dictionary)MyStringToStringGenericDict)["key"]); + Assert.Equal("value", ((IDictionary)MyStringToStringGenericIDict)["key"]); + Assert.Equal("value", ((IReadOnlyDictionary)MyStringToStringGenericIReadOnlyDict)["key"]); Assert.Equal("value", ((ImmutableDictionary)MyStringToStringImmutableDict)["key"]); Assert.Equal("value", ((IImmutableDictionary)MyStringToStringIImmutableDict)["key"]); Assert.Equal("value", ((ImmutableSortedDictionary)MyStringToStringImmutableSortedDict)["key"]); + Assert.Equal("myKey", ((KeyValuePair)MyStringToStringKeyValuePair).Key); + Assert.Equal("myValue", ((KeyValuePair)MyStringToStringKeyValuePair).Value); + Assert.Equal(2, ((Stack)MyStringStackT).Count); Assert.True(((Stack)MyStringStackT).Contains("Hello")); Assert.True(((Stack)MyStringStackT).Contains("World")); diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestStruct.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestStruct.cs index 3a40d7f..0c7d9e1 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestStruct.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestStruct.cs @@ -52,6 +52,7 @@ namespace System.Text.Json.Serialization.Tests public ICollection MyStringICollectionT { get; set; } public IReadOnlyCollection MyStringIReadOnlyCollectionT { get; set; } public IReadOnlyList MyStringIReadOnlyListT { get; set; } + public ISet MyStringISetT { get; set; } public static readonly string s_json = $"{{{s_partialJsonProperties},{s_partialJsonArrays}}}"; public static readonly string s_json_flipped = $"{{{s_partialJsonArrays},{s_partialJsonProperties}}}"; @@ -100,7 +101,8 @@ namespace System.Text.Json.Serialization.Tests @"""MyStringIListT"" : [""Hello""]," + @"""MyStringICollectionT"" : [""Hello""]," + @"""MyStringIReadOnlyCollectionT"" : [""Hello""]," + - @"""MyStringIReadOnlyListT"" : [""Hello""]"; + @"""MyStringIReadOnlyListT"" : [""Hello""]," + + @"""MyStringISetT"" : [""Hello""]"; public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); @@ -150,6 +152,7 @@ namespace System.Text.Json.Serialization.Tests MyStringICollectionT = new string[] { "Hello" }; MyStringIReadOnlyCollectionT = new string[] { "Hello" }; MyStringIReadOnlyListT = new string[] { "Hello" }; + MyStringISetT = new HashSet { "Hello" }; } public void Verify() @@ -198,6 +201,7 @@ namespace System.Text.Json.Serialization.Tests Assert.Equal("Hello", MyStringICollectionT.First()); Assert.Equal("Hello", MyStringIReadOnlyCollectionT.First()); Assert.Equal("Hello", MyStringIReadOnlyListT[0]); + Assert.Equal("Hello", MyStringISetT.First()); } } } diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs index db237f8..ed9f6ba 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs @@ -2,9 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using Xunit; namespace System.Text.Json.Serialization.Tests @@ -334,6 +334,149 @@ namespace System.Text.Json.Serialization.Tests } } + public class TestClassWithObjectIEnumerable : ITestClass + { + public IEnumerable MyData { get; set; } + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":[" + + SimpleTestClass.s_json + "," + + SimpleTestClass.s_json + + @"]" + + @"}"); + + public void Initialize() + { + SimpleTestClass obj1 = new SimpleTestClass(); + obj1.Initialize(); + + SimpleTestClass obj2 = new SimpleTestClass(); + obj2.Initialize(); + + MyData = new SimpleTestClass[] { obj1, obj2 }; + } + + public void Verify() + { + int count = 0; + foreach (object data in MyData) + { + if (data is JsonElement element) + { + SimpleTestClass obj = JsonSerializer.Parse(element.GetRawText()); + obj.Verify(); + } + else + { + ((SimpleTestClass)data).Verify(); + } + count++; + } + Assert.Equal(2, count); + } + } + + public class TestClassWithObjectIList : ITestClass + { + public IList MyData { get; set; } + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":[" + + SimpleTestClass.s_json + "," + + SimpleTestClass.s_json + + @"]" + + @"}"); + + public void Initialize() + { + MyData = new List(); + + { + SimpleTestClass obj = new SimpleTestClass(); + obj.Initialize(); + MyData.Add(obj); + } + + { + SimpleTestClass obj = new SimpleTestClass(); + obj.Initialize(); + MyData.Add(obj); + } + } + + public void Verify() + { + int count = 0; + foreach (object data in MyData) + { + if (data is JsonElement element) + { + SimpleTestClass obj = JsonSerializer.Parse(element.GetRawText()); + obj.Verify(); + } + else + { + ((SimpleTestClass)data).Verify(); + } + count++; + } + Assert.Equal(2, count); + } + } + + public class TestClassWithObjectICollection : ITestClass + { + public ICollection MyData { get; set; } + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":[" + + SimpleTestClass.s_json + "," + + SimpleTestClass.s_json + + @"]" + + @"}"); + + public void Initialize() + { + List dataTemp = new List(); + + { + SimpleTestClass obj = new SimpleTestClass(); + obj.Initialize(); + dataTemp.Add(obj); + } + + { + SimpleTestClass obj = new SimpleTestClass(); + obj.Initialize(); + dataTemp.Add(obj); + } + + MyData = dataTemp; + } + + public void Verify() + { + int count = 0; + foreach (object data in MyData) + { + if (data is JsonElement element) + { + SimpleTestClass obj = JsonSerializer.Parse(element.GetRawText()); + obj.Verify(); + } + else + { + ((SimpleTestClass)data).Verify(); + } + count++; + } + Assert.Equal(2, count); + } + } + public class TestClassWithObjectIReadOnlyCollectionT : ITestClass { public IReadOnlyCollection MyData { get; set; } @@ -399,6 +542,40 @@ namespace System.Text.Json.Serialization.Tests } } + public class TestClassWithObjectISetT : ITestClass + { + public ISet MyData { get; set; } + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":[" + + SimpleTestClass.s_json + "," + + SimpleTestClass.s_json + + @"]" + + @"}"); + + public void Initialize() + { + SimpleTestClass obj1 = new SimpleTestClass(); + obj1.Initialize(); + + SimpleTestClass obj2 = new SimpleTestClass(); + obj2.Initialize(); + + MyData = new HashSet { obj1, obj2 }; + } + + public void Verify() + { + Assert.Equal(2, MyData.Count); + + foreach (SimpleTestClass obj in MyData) + { + obj.Verify(); + } + } + } + public class TestClassWithStringArray : ITestClass { public string[] MyData { get; set; } @@ -454,6 +631,138 @@ namespace System.Text.Json.Serialization.Tests } } + public class TestClassWithGenericIEnumerable : ITestClass + { + public IEnumerable MyData { get; set; } + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":[" + + @"""Hello""," + + @"""World""" + + @"]" + + @"}"); + + public void Initialize() + { + MyData = new List + { + "Hello", + "World" + }; + + int count = 0; + foreach (string data in MyData) + { + count++; + } + Assert.Equal(2, count); + } + + public void Verify() + { + string[] expected = { "Hello", "World" }; + int count = 0; + foreach (object data in MyData) + { + if (data is JsonElement element) + { + Assert.Equal(expected[count], element.GetString()); + } + else + { + Assert.Equal(expected[count], (string)data); + } + count++; + } + Assert.Equal(2, count); + } + } + + public class TestClassWithGenericIList : ITestClass + { + public IList MyData { get; set; } + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":[" + + @"""Hello""," + + @"""World""" + + @"]" + + @"}"); + + public void Initialize() + { + MyData = new List + { + "Hello", + "World" + }; + Assert.Equal(2, MyData.Count); + } + + public void Verify() + { + string[] expected = { "Hello", "World" }; + int count = 0; + foreach (object data in MyData) + { + if (data is JsonElement element) + { + Assert.Equal(expected[count], element.GetString()); + } + else + { + Assert.Equal(expected[count], (string)data); + } + count++; + } + Assert.Equal(2, count); + } + } + + public class TestClassWithGenericICollection : ITestClass + { + public ICollection MyData { get; set; } + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":[" + + @"""Hello""," + + @"""World""" + + @"]" + + @"}"); + + public void Initialize() + { + MyData = new List + { + "Hello", + "World" + }; + Assert.Equal(2, MyData.Count); + } + + public void Verify() + { + string[] expected = { "Hello", "World" }; + int count = 0; + foreach (object data in MyData) + { + if (data is JsonElement element) + { + Assert.Equal(expected[count], element.GetString()); + } + else + { + Assert.Equal(expected[count], (string)data); + } + count++; + } + Assert.Equal(2, count); + } + } + public class TestClassWithGenericIEnumerableT : ITestClass { public IEnumerable MyData { get; set; } @@ -629,6 +938,51 @@ namespace System.Text.Json.Serialization.Tests } } + public class TestClassWithGenericISetT : ITestClass + { + public ISet MyData { get; set; } + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":[" + + @"""Hello""," + + @"""World""" + + @"]" + + @"}"); + + public void Initialize() + { + MyData = new HashSet + { + "Hello", + "World" + }; + Assert.Equal(2, MyData.Count); + } + + public void Verify() + { + Assert.Equal(2, MyData.Count); + + bool helloSeen = false; + bool worldSeen = false; + + foreach (string data in MyData) + { + if (data == "Hello") + { + helloSeen = true; + } + else if (data == "World") + { + worldSeen = true; + } + } + + Assert.True(helloSeen && worldSeen); + } + } + public class TestClassWithStringToPrimitiveDictionary : ITestClass { public Dictionary MyInt32Dict { get; set; } diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestData.cs b/src/libraries/System.Text.Json/tests/Serialization/TestData.cs index 8be65ec..8bd6cb7 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/TestData.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/TestData.cs @@ -23,18 +23,26 @@ namespace System.Text.Json.Serialization.Tests yield return new object[] { typeof(TestClassWithNestedObjectInner), TestClassWithNestedObjectInner.s_data }; yield return new object[] { typeof(TestClassWithNestedObjectOuter), TestClassWithNestedObjectOuter.s_data }; yield return new object[] { typeof(TestClassWithObjectArray), TestClassWithObjectArray.s_data }; + yield return new object[] { typeof(TestClassWithObjectIEnumerable), TestClassWithObjectIEnumerable.s_data }; + yield return new object[] { typeof(TestClassWithObjectIList), TestClassWithObjectIList.s_data }; + yield return new object[] { typeof(TestClassWithObjectICollection), TestClassWithObjectICollection.s_data }; yield return new object[] { typeof(TestClassWithObjectIEnumerableT), TestClassWithObjectIEnumerableT.s_data }; yield return new object[] { typeof(TestClassWithObjectIListT), TestClassWithObjectIListT.s_data }; yield return new object[] { typeof(TestClassWithObjectICollectionT), TestClassWithObjectICollectionT.s_data }; yield return new object[] { typeof(TestClassWithObjectIReadOnlyCollectionT), TestClassWithObjectIReadOnlyCollectionT.s_data }; yield return new object[] { typeof(TestClassWithObjectIReadOnlyListT), TestClassWithObjectIReadOnlyListT.s_data }; + yield return new object[] { typeof(TestClassWithObjectISetT), TestClassWithObjectISetT.s_data }; yield return new object[] { typeof(TestClassWithStringArray), TestClassWithStringArray.s_data }; yield return new object[] { typeof(TestClassWithGenericList), TestClassWithGenericList.s_data }; + yield return new object[] { typeof(TestClassWithGenericIEnumerable), TestClassWithGenericIEnumerable.s_data }; + yield return new object[] { typeof(TestClassWithGenericIList), TestClassWithGenericIList.s_data }; + yield return new object[] { typeof(TestClassWithGenericICollection), TestClassWithGenericICollection.s_data }; yield return new object[] { typeof(TestClassWithGenericIEnumerableT), TestClassWithGenericIEnumerableT.s_data }; yield return new object[] { typeof(TestClassWithGenericIListT), TestClassWithGenericIListT.s_data }; yield return new object[] { typeof(TestClassWithGenericICollectionT), TestClassWithGenericICollectionT.s_data }; yield return new object[] { typeof(TestClassWithGenericIReadOnlyCollectionT), TestClassWithGenericIReadOnlyCollectionT.s_data }; yield return new object[] { typeof(TestClassWithGenericIReadOnlyListT), TestClassWithGenericIReadOnlyListT.s_data }; + yield return new object[] { typeof(TestClassWithGenericISetT), TestClassWithGenericISetT.s_data }; yield return new object[] { typeof(TestClassWithStringToPrimitiveDictionary), TestClassWithStringToPrimitiveDictionary.s_data }; yield return new object[] { typeof(TestClassWithObjectIEnumerableConstructibleTypes), TestClassWithObjectIEnumerableConstructibleTypes.s_data }; yield return new object[] { typeof(TestClassWithObjectImmutableTypes), TestClassWithObjectImmutableTypes.s_data }; @@ -58,18 +66,26 @@ namespace System.Text.Json.Serialization.Tests yield return new object[] { new TestClassWithNestedObjectInner() }; yield return new object[] { new TestClassWithNestedObjectOuter() }; yield return new object[] { new TestClassWithObjectArray() }; + yield return new object[] { new TestClassWithObjectIEnumerable() }; + yield return new object[] { new TestClassWithObjectIList() }; + yield return new object[] { new TestClassWithObjectICollection() }; yield return new object[] { new TestClassWithObjectIEnumerableT() }; yield return new object[] { new TestClassWithObjectIListT() }; yield return new object[] { new TestClassWithObjectICollectionT() }; yield return new object[] { new TestClassWithObjectIReadOnlyCollectionT() }; yield return new object[] { new TestClassWithObjectIReadOnlyListT() }; + yield return new object[] { new TestClassWithObjectISetT() }; yield return new object[] { new TestClassWithStringArray() }; yield return new object[] { new TestClassWithGenericList() }; + yield return new object[] { new TestClassWithGenericIEnumerable() }; + yield return new object[] { new TestClassWithGenericIList() }; + yield return new object[] { new TestClassWithGenericICollection() }; yield return new object[] { new TestClassWithGenericIEnumerableT() }; yield return new object[] { new TestClassWithGenericIListT() }; yield return new object[] { new TestClassWithGenericICollectionT() }; yield return new object[] { new TestClassWithGenericIReadOnlyCollectionT() }; yield return new object[] { new TestClassWithGenericIReadOnlyListT() }; + yield return new object[] { new TestClassWithGenericISetT() }; yield return new object[] { new TestClassWithStringToPrimitiveDictionary() }; yield return new object[] { new TestClassWithObjectIEnumerableConstructibleTypes() }; yield return new object[] { new TestClassWithObjectImmutableTypes() }; diff --git a/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.GenericCollections.cs b/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.GenericCollections.cs index ca26eeb..41dffa1 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.GenericCollections.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.GenericCollections.cs @@ -355,6 +355,94 @@ namespace System.Text.Json.Serialization.Tests } [Fact] + public static void ReadISetTOfISetT_Throws() + { + ISet> result = JsonSerializer.Parse>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + + if (result.First().Contains(1)) + { + Assert.Equal(new HashSet { 1, 2 }, result.First()); + Assert.Equal(new HashSet { 3, 4 }, result.Last()); + } + else + { + Assert.Equal(new HashSet { 3, 4 }, result.First()); + Assert.Equal(new HashSet { 1, 2 }, result.Last()); + } + } + + [Fact] + public static void ReadISetTOfHashSetT() + { + ISet> result = JsonSerializer.Parse>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + + if (result.First().Contains(1)) + { + Assert.Equal(new HashSet { 1, 2 }, result.First()); + Assert.Equal(new HashSet { 3, 4 }, result.Last()); + } + else + { + Assert.Equal(new HashSet { 3, 4 }, result.First()); + Assert.Equal(new HashSet { 1, 2 }, result.Last()); + } + } + + [Fact] + public static void ReadHashSetTOfISetT() + { + HashSet> result = JsonSerializer.Parse>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + + if (result.First().Contains(1)) + { + Assert.Equal(new HashSet { 1, 2 }, result.First()); + Assert.Equal(new HashSet { 3, 4 }, result.Last()); + } + else + { + Assert.Equal(new HashSet { 3, 4 }, result.First()); + Assert.Equal(new HashSet { 1, 2 }, result.Last()); + } + } + + [Fact] + public static void ReadISetTOfArray() + { + ISet result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + + if (result.First().Contains(1)) + { + Assert.Equal(new HashSet { 1, 2 }, result.First()); + Assert.Equal(new HashSet { 3, 4 }, result.Last()); + } + else + { + Assert.Equal(new HashSet { 3, 4 }, result.First()); + Assert.Equal(new HashSet { 1, 2 }, result.Last()); + } + } + + [Fact] + public static void ReadArrayOfISetT() + { + ISet[] result = JsonSerializer.Parse[]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + + Assert.Equal(new HashSet { 1, 2 }, result.First()); + Assert.Equal(new HashSet { 3, 4 }, result.Last()); + } + + [Fact] + public static void ReadPrimitiveISetT() + { + ISet result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[1,2]")); + + Assert.Equal(new HashSet { 1, 2 }, result); + + result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[]")); + Assert.Equal(0, result.Count()); + } + + [Fact] public static void StackTOfStackT() { Stack> result = JsonSerializer.Parse>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); @@ -627,5 +715,75 @@ namespace System.Text.Json.Serialization.Tests result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[]")); Assert.Equal(0, result.Count()); } + + [Fact] + public static void ReadPrimitiveKeyValuePair() + { + KeyValuePair input = JsonSerializer.Parse>(@"{""Key"": 123}"); + + Assert.Equal(input.Key, "Key"); + Assert.Equal(input.Value, 123); + + input = JsonSerializer.Parse>(@"{""Key"": ""Key"", ""Value"": 123}"); + + Assert.Equal(input.Key, "Key"); + Assert.Equal(input.Value, 123); + + // Invalid form: extra property + Assert.Throws(() => JsonSerializer.Parse>(@"{""Key"": ""Key"", ""Value"": 123, ""Value2"": 456}")); + + // Invalid form: does not contain both Key and Value properties + Assert.Throws(() => JsonSerializer.Parse>(@"{""Key"": ""Key"", ""Val"": 123")); + } + + [Fact] + public static void ReadListOfKeyValuePair() + { + List> input = JsonSerializer.Parse>>(@"[{""123"":123},{""456"": 456}]"); + + Assert.Equal(2, input.Count); + Assert.Equal("123", input[0].Key); + Assert.Equal(123, input[0].Value); + Assert.Equal("456", input[1].Key); + Assert.Equal(456, input[1].Value); + + input = JsonSerializer.Parse>>(@"[{""Key"":""123"",""Value"": 123},{""Key"": ""456"",""Value"": 456}]"); + + Assert.Equal(2, input.Count); + Assert.Equal("123", input[0].Key); + Assert.Equal(123, input[0].Value); + Assert.Equal("456", input[1].Key); + Assert.Equal(456, input[1].Value); + } + + [Fact] + public static void ReadKeyValuePairOfList() + { + KeyValuePair> input = JsonSerializer.Parse>>(@"{""Key"":[1, 2, 3]}"); + + Assert.Equal("Key", input.Key); + Assert.Equal(3, input.Value.Count); + Assert.Equal(1, input.Value[0]); + Assert.Equal(2, input.Value[1]); + Assert.Equal(3, input.Value[2]); + + input = JsonSerializer.Parse>>(@"{""Key"": ""Key"", ""Value"": [1, 2, 3]}"); + + Assert.Equal("Key", input.Key); + Assert.Equal(3, input.Value.Count); + Assert.Equal(1, input.Value[0]); + Assert.Equal(2, input.Value[1]); + Assert.Equal(3, input.Value[2]); + } + + [Fact] + public static void ReadKeyValuePairOfKeyValuePair() + { + KeyValuePair> input = JsonSerializer.Parse>>(@"{""Key"":{""Key"":1}}"); + + Assert.Equal("Key", input.Key); + Assert.Equal("Key", input.Value.Key); + Assert.Equal(1, input.Value.Value); + } } } diff --git a/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.NonGenericCollections.cs b/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.NonGenericCollections.cs new file mode 100644 index 0000000..ee0e2cc --- /dev/null +++ b/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.NonGenericCollections.cs @@ -0,0 +1,398 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace System.Text.Json.Serialization.Tests +{ + public static partial class ValueTests + { + public static void ReadGenericIEnumerableOfIEnumerable() + { + IEnumerable result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (IEnumerable ie in result) + { + foreach (JsonElement i in ie) + { + Assert.Equal(expected++, i.GetInt32()); + } + } + } + + [Fact] + public static void ReadIEnumerableOfArray() + { + IEnumerable result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (JsonElement arr in result) + { + foreach (JsonElement i in arr.EnumerateArray()) + { + Assert.Equal(expected++, i.GetInt32()); + } + } + } + + [Fact] + public static void ReadArrayOfIEnumerable() + { + IEnumerable[] result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (IEnumerable arr in result) + { + foreach (JsonElement i in arr) + { + Assert.Equal(expected++, i.GetInt32()); + } + } + } + + [Fact] + public static void ReadPrimitiveIEnumerable() + { + IEnumerable result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[1,2]")); + int expected = 1; + + foreach (JsonElement i in result) + { + Assert.Equal(expected++, i.GetInt32()); + } + + result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[]")); + + int count = 0; + IEnumerator e = result.GetEnumerator(); + while (e.MoveNext()) + { + count++; + } + Assert.Equal(0, count); + } + + public static void ReadGenericIListOfIList() + { + IList result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (IList list in result) + { + foreach (JsonElement i in list) + { + Assert.Equal(expected++, i.GetInt32()); + } + } + } + + [Fact] + public static void ReadIListOfArray() + { + IList result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (JsonElement arr in result) + { + foreach (JsonElement i in arr.EnumerateArray()) + { + Assert.Equal(expected++, i.GetInt32()); + } + } + } + + [Fact] + public static void ReadArrayOfIList() + { + IList[] result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (IList arr in result) + { + foreach (JsonElement i in arr) + { + Assert.Equal(expected++, i.GetInt32()); + } + } + } + + [Fact] + public static void ReadPrimitiveIList() + { + IList result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[1,2]")); + int expected = 1; + + foreach (JsonElement i in result) + { + Assert.Equal(expected++, i.GetInt32()); + } + + result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[]")); + + int count = 0; + IEnumerator e = result.GetEnumerator(); + while (e.MoveNext()) + { + count++; + } + Assert.Equal(0, count); + } + + public static void ReadGenericICollectionOfICollection() + { + ICollection result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (ICollection ie in result) + { + foreach (JsonElement i in ie) + { + Assert.Equal(expected++, i.GetInt32()); + } + } + } + + [Fact] + public static void ReadICollectionOfArray() + { + ICollection result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (JsonElement arr in result) + { + foreach (JsonElement i in arr.EnumerateArray()) + { + Assert.Equal(expected++, i.GetInt32()); + } + } + } + + [Fact] + public static void ReadArrayOfICollection() + { + ICollection[] result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (ICollection arr in result) + { + foreach (JsonElement i in arr) + { + Assert.Equal(expected++, i.GetInt32()); + } + } + } + + [Fact] + public static void ReadPrimitiveICollection() + { + ICollection result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[1,2]")); + int expected = 1; + + foreach (JsonElement i in result) + { + Assert.Equal(expected++, i.GetInt32()); + } + + result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[]")); + + int count = 0; + IEnumerator e = result.GetEnumerator(); + while (e.MoveNext()) + { + count++; + } + Assert.Equal(0, count); + } + + public static void ReadGenericStackOfStack() + { + Stack result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 4; + + foreach (Stack stack in result) + { + foreach (JsonElement i in stack) + { + Assert.Equal(expected--, i.GetInt32()); + } + } + } + + [Fact] + public static void ReadStackOfArray() + { + Stack result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 3; + + foreach (JsonElement arr in result) + { + foreach (JsonElement i in arr.EnumerateArray()) + { + Assert.Equal(expected++, i.GetInt32()); + } + expected = 1; + } + } + + [Fact] + public static void ReadArrayOfStack() + { + Stack[] result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 2; + + foreach (Stack arr in result) + { + foreach (JsonElement i in arr) + { + Assert.Equal(expected--, i.GetInt32()); + } + expected = 4; + } + } + + [Fact] + public static void ReadPrimitiveStack() + { + Stack result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[1,2]")); + int expected = 2; + + foreach (JsonElement i in result) + { + Assert.Equal(expected--, i.GetInt32()); + } + + result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[]")); + + int count = 0; + IEnumerator e = result.GetEnumerator(); + while (e.MoveNext()) + { + count++; + } + Assert.Equal(0, count); + } + + public static void ReadGenericQueueOfQueue() + { + Queue result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (Queue ie in result) + { + foreach (JsonElement i in ie) + { + Assert.Equal(expected++, i.GetInt32()); + } + } + } + + [Fact] + public static void ReadQueueOfArray() + { + Queue result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (JsonElement arr in result) + { + foreach (JsonElement i in arr.EnumerateArray()) + { + Assert.Equal(expected++, i.GetInt32()); + } + } + } + + [Fact] + public static void ReadArrayOfQueue() + { + Queue[] result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (Queue arr in result) + { + foreach (JsonElement i in arr) + { + Assert.Equal(expected++, i.GetInt32()); + } + } + } + + [Fact] + public static void ReadPrimitiveQueue() + { + Queue result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[1,2]")); + int expected = 1; + + foreach (JsonElement i in result) + { + Assert.Equal(expected++, i.GetInt32()); + } + + result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[]")); + + int count = 0; + IEnumerator e = result.GetEnumerator(); + while (e.MoveNext()) + { + count++; + } + Assert.Equal(0, count); + } + + [Fact] + public static void ReadArrayListOfArray() + { + ArrayList result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (JsonElement arr in result) + { + foreach (JsonElement i in arr.EnumerateArray()) + { + Assert.Equal(expected++, i.GetInt32()); + } + } + } + + [Fact] + public static void ReadArrayOfArrayList() + { + ArrayList[] result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (ArrayList arr in result) + { + foreach (JsonElement i in arr) + { + Assert.Equal(expected++, i.GetInt32()); + } + } + } + + [Fact] + public static void ReadPrimitiveArrayList() + { + ArrayList result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[1,2]")); + int expected = 1; + + foreach (JsonElement i in result) + { + Assert.Equal(expected++, i.GetInt32()); + } + + result = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[]")); + + int count = 0; + IEnumerator e = result.GetEnumerator(); + while (e.MoveNext()) + { + count++; + } + Assert.Equal(0, count); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.GenericCollections.cs b/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.GenericCollections.cs index 8128e44..e8a8818 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.GenericCollections.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.GenericCollections.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Linq; using Xunit; namespace System.Text.Json.Serialization.Tests @@ -286,6 +287,122 @@ namespace System.Text.Json.Serialization.Tests } [Fact] + public static void WriteISetTOfISetT() + { + ISet> input = new HashSet> + { + new HashSet() { 1, 2 }, + new HashSet() { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + + // Because order isn't guaranteed, roundtrip data to ensure write was accurate. + input = JsonSerializer.Parse>>(json); + + if (input.First().Contains(1)) + { + Assert.Equal(new HashSet { 1, 2 }, input.First()); + Assert.Equal(new HashSet { 3, 4 }, input.Last()); + } + else + { + Assert.Equal(new HashSet { 3, 4 }, input.First()); + Assert.Equal(new HashSet { 1, 2 }, input.Last()); + } + } + + [Fact] + public static void WriteISetTOfHashSetT() + { + ISet> input = new HashSet> + { + new HashSet() { 1, 2 }, + new HashSet() { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + + // Because order isn't guaranteed, roundtrip data to ensure write was accurate. + input = JsonSerializer.Parse>>(json); + + if (input.First().Contains(1)) + { + Assert.Equal(new HashSet { 1, 2 }, input.First()); + Assert.Equal(new HashSet { 3, 4 }, input.Last()); + } + else + { + Assert.Equal(new HashSet { 3, 4 }, input.First()); + Assert.Equal(new HashSet { 1, 2 }, input.Last()); + } + } + + [Fact] + public static void WriteHashSetTOfISetT() + { + HashSet> input = new HashSet> + { + new HashSet() { 1, 2 }, + new HashSet() { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + + // Because order isn't guaranteed, roundtrip data to ensure write was accurate. + input = JsonSerializer.Parse>>(json); + + if (input.First().Contains(1)) + { + Assert.Equal(new HashSet { 1, 2 }, input.First()); + Assert.Equal(new HashSet { 3, 4 }, input.Last()); + } + else + { + Assert.Equal(new HashSet { 3, 4 }, input.First()); + Assert.Equal(new HashSet { 1, 2 }, input.Last()); + } + } + + [Fact] + public static void WriteISetTOfArray() + { + ISet input = new HashSet + { + new int[] { 1, 2 }, + new int[] { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + Assert.True(json.Contains("[1,2]")); + Assert.True(json.Contains("[3,4]")); + } + + [Fact] + public static void WriteArrayOfISetT() + { + ISet[] input = new HashSet[2]; + input[0] = new HashSet() { 1, 2 }; + input[1] = new HashSet() { 3, 4 }; + + string json = JsonSerializer.ToString(input); + + // Because order isn't guaranteed, roundtrip data to ensure write was accurate. + input = JsonSerializer.Parse[]>(json); + Assert.Equal(new HashSet { 1, 2 }, input.First()); + Assert.Equal(new HashSet { 3, 4 }, input.Last()); + } + + [Fact] + public static void WritePrimitiveISetT() + { + ISet input = new HashSet { 1, 2 }; + + string json = JsonSerializer.ToString(input); + Assert.True(json == "[1,2]" || json == "[2,1]"); + } + + [Fact] public static void WriteStackTOfStackT() { Stack> input = new Stack>(new List> @@ -387,7 +504,20 @@ namespace System.Text.Json.Serialization.Tests }); string json = JsonSerializer.ToString(input); - Assert.Equal("[[1,2],[3,4]]", json); + + // Because order isn't guaranteed, roundtrip data to ensure write was accurate. + input = JsonSerializer.Parse>>(json); + + if (input.First().Contains(1)) + { + Assert.Equal(new HashSet { 1, 2 }, input.First()); + Assert.Equal(new HashSet { 3, 4 }, input.Last()); + } + else + { + Assert.Equal(new HashSet { 3, 4 }, input.First()); + Assert.Equal(new HashSet { 1, 2 }, input.Last()); + } } [Fact] @@ -400,7 +530,8 @@ namespace System.Text.Json.Serialization.Tests }); string json = JsonSerializer.ToString(input); - Assert.Equal("[[1,2],[3,4]]", json); + Assert.True(json.Contains("[1,2]")); + Assert.True(json.Contains("[3,4]")); } [Fact] @@ -411,7 +542,11 @@ namespace System.Text.Json.Serialization.Tests input[1] = new HashSet(new List { 3, 4 }); string json = JsonSerializer.ToString(input); - Assert.Equal("[[1,2],[3,4]]", json); + + // Because order isn't guaranteed, roundtrip data to ensure write was accurate. + input = JsonSerializer.Parse[]>(json); + Assert.Equal(new HashSet { 1, 2 }, input.First()); + Assert.Equal(new HashSet { 3, 4 }, input.Last()); } [Fact] @@ -420,7 +555,7 @@ namespace System.Text.Json.Serialization.Tests HashSet input = new HashSet(new List { 1, 2 }); string json = JsonSerializer.ToString(input); - Assert.Equal("[1,2]", json); + Assert.True(json == "[1,2]" || json == "[2,1]"); } [Fact] @@ -488,5 +623,46 @@ namespace System.Text.Json.Serialization.Tests string json = JsonSerializer.ToString(input); Assert.Equal("[1,2]", json); } + + [Fact] + public static void WritePrimitiveKeyValuePair() + { + KeyValuePair input = new KeyValuePair("Key", 123) ; + + string json = JsonSerializer.ToString(input); + Assert.Equal(@"{""Key"":""Key"",""Value"":123}", json); + } + + [Fact] + public static void WriteListOfKeyValuePair() + { + List> input = new List> + { + new KeyValuePair("123", 123), + new KeyValuePair("456", 456) + }; + + string json = JsonSerializer.ToString(input); + Assert.Equal(@"[{""Key"":""123"",""Value"":123},{""Key"":""456"",""Value"":456}]", json); + } + + [Fact] + public static void WriteKeyValuePairOfList() + { + KeyValuePair> input = new KeyValuePair>("Key", new List { 1, 2, 3 }); + + string json = JsonSerializer.ToString(input); + Assert.Equal(@"{""Key"":""Key"",""Value"":[1,2,3]}", json); + } + + [Fact] + public static void WriteKeyValuePairOfKeyValuePair() + { + KeyValuePair> input = new KeyValuePair>( + "Key", new KeyValuePair("Key", 1)); + + string json = JsonSerializer.ToString(input); + Assert.Equal(@"{""Key"":""Key"",""Value"":{""Key"":""Key"",""Value"":1}}", json); + } } } diff --git a/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.NonGenericCollections.cs b/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.NonGenericCollections.cs new file mode 100644 index 0000000..1bcd0d5 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.NonGenericCollections.cs @@ -0,0 +1,263 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.Generic; +using Xunit; + +namespace System.Text.Json.Serialization.Tests +{ + public static partial class ValueTests + { + [Fact] + public static void WriteIEnumerableOfIEnumerable() + { + IEnumerable input = new List> + { + new List() { 1, 2 }, + new List() { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + public static void WriteGenericIEnumerableOfIEnumerable() + { + IEnumerable input = new List + { + new List() { 1, 2 }, + new List() { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WriteArrayOfIEnumerable() + { + IEnumerable[] input = new IEnumerable[2]; + input[0] = new List() { 1, 2 }; + input[1] = new List() { 3, 4 }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WritePrimitiveIEnumerable() + { + IEnumerable input = new List { 1, 2 }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[1,2]", json); + } + + [Fact] + public static void WriteIListOfIList() + { + IList input = new List + { + new List() { 1, 2 }, + new List() { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + public static void WriteIListGenericOfIList() + { + IList input = new List + { + new List() { 1, 2 }, + new List() { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WriteArrayOfIList() + { + IList[] input = new IList[2]; + input[0] = new List() { 1, 2 }; + input[1] = new List() { 3, 4 }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WritePrimitiveIList() + { + IList input = new List { 1, 2 }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[1,2]", json); + } + + [Fact] + public static void WriteICollectionOfICollection() + { + ICollection input = new List + { + new List() { 1, 2 }, + new List() { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + public static void WriteGenericICollectionOfICollection() + { + ICollection input = new List + { + new List() { 1, 2 }, + new List() { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WriteArrayOfICollection() + { + ICollection[] input = new List[2]; + input[0] = new List() { 1, 2 }; + input[1] = new List() { 3, 4 }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WritePrimitiveICollection() + { + ICollection input = new List { 1, 2 }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[1,2]", json); + } + + [Fact] + public static void WriteStackOfStack() + { + Stack input = new Stack(); + input.Push(new Stack(new List() { 1, 2 })); + input.Push(new Stack(new List() { 3, 4 })); + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[4,3],[2,1]]", json); + } + + public static void WriteGenericStackOfStack() + { + Stack input = new Stack(); + input.Push(new Stack(new List() { 1, 2 })); + input.Push(new Stack(new List() { 3, 4 })); + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[4,3],[2,1]]", json); + } + + [Fact] + public static void WriteArrayOfStack() + { + Stack[] input = new Stack[2]; + input[0] = new Stack(new List() { 1, 2 }); + input[1] = new Stack(new List() { 3, 4 }); + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[2,1],[4,3]]", json); + } + + [Fact] + public static void WritePrimitiveStack() + { + Stack input = new Stack( new List { 1, 2 }); + + string json = JsonSerializer.ToString(input); + Assert.Equal("[2,1]", json); + } + + [Fact] + public static void WriteQueueOfQueue() + { + Queue input = new Queue(); + input.Enqueue(new Queue(new List() { 1, 2 })); + input.Enqueue(new Queue(new List() { 3, 4 })); + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + public static void WriteGenericQueueOfQueue() + { + Queue input = new Queue(); + input.Enqueue(new Queue(new List() { 1, 2 })); + input.Enqueue(new Queue(new List() { 3, 4 })); + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WriteArrayOfQueue() + { + Queue[] input = new Queue[2]; + input[0] = new Queue(new List() { 1, 2 }); + input[1] = new Queue(new List() { 3, 4 }); + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WritePrimitiveQueue() + { + Queue input = new Queue(new List { 1, 2 }); + + string json = JsonSerializer.ToString(input); + Assert.Equal("[1,2]", json); + } + + [Fact] + public static void WriteArrayListOfArrayList() + { + ArrayList input = new ArrayList + { + new ArrayList(new List() { 1, 2 }), + new ArrayList(new List() { 3, 4 }) + }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WriteArrayOfArrayList() + { + ArrayList[] input = new ArrayList[2]; + input[0] = new ArrayList(new List() { 1, 2 }); + input[1] = new ArrayList(new List() { 3, 4 }); + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WritePrimitiveArrayList() + { + ArrayList input = new ArrayList(new List { 1, 2 }); + + string json = JsonSerializer.ToString(input); + Assert.Equal("[1,2]", json); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj index c84d8c5..089f72c 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj @@ -64,9 +64,11 @@ + + -- 2.7.4