diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryConverter.cs index 6c7da24e46e927..c989e5e4aaf33c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryConverter.cs @@ -40,6 +40,7 @@ protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStac ThrowHelper.ThrowNotSupportedException_CannotPopulateCollection(TypeToConvert, ref reader, ref state); } + // Strings are intentionally used as keys when deserializing non-generic dictionaries. state.Current.ReturnValue = new Dictionary(); } else diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryOfTKeyTValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryOfTKeyTValueConverter.cs index 2d912594f6996c..da9dcf1040d365 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryOfTKeyTValueConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryOfTKeyTValueConverter.cs @@ -6,7 +6,7 @@ namespace System.Text.Json.Serialization.Converters { /// - /// Converter for System.Collections.Generic.IDictionary{string, TValue} that + /// Converter for System.Collections.Generic.IDictionary{TKey, TValue} that /// (de)serializes as a JSON object with properties representing the dictionary element key and value. /// internal sealed class IDictionaryOfTKeyTValueConverter @@ -35,7 +35,7 @@ protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStac ThrowHelper.ThrowNotSupportedException_CannotPopulateCollection(TypeToConvert, ref reader, ref state); } - state.Current.ReturnValue = new Dictionary(); + state.Current.ReturnValue = new Dictionary(); } else { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactoryHelpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactoryHelpers.cs index 4d8be8bec4c62f..92f9bd1d85cd87 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactoryHelpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactoryHelpers.cs @@ -171,7 +171,7 @@ public static MethodInfo GetImmutableEnumerableCreateRangeMethod(this Type type, [DynamicDependency(CreateRangeMethodNameForDictionary, ImmutableDictionaryTypeName, ImmutableCollectionsAssembly)] [DynamicDependency(CreateRangeMethodNameForDictionary, ImmutableSortedDictionaryTypeName, ImmutableCollectionsAssembly)] - public static MethodInfo GetImmutableDictionaryCreateRangeMethod(this Type type, Type elementType) + public static MethodInfo GetImmutableDictionaryCreateRangeMethod(this Type type, Type keyType, Type valueType) { Type? constructingType = GetImmutableDictionaryConstructingType(type); if (constructingType != null) @@ -184,7 +184,7 @@ public static MethodInfo GetImmutableDictionaryCreateRangeMethod(this Type type, method.IsGenericMethod && method.GetGenericArguments().Length == 2) { - return method.MakeGenericMethod(typeof(string), elementType); + return method.MakeGenericMethod(keyType, valueType); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfTKeyTValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfTKeyTValueConverter.cs index 28c3c245b1c23b..f8156f51c181c6 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfTKeyTValueConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfTKeyTValueConverter.cs @@ -22,7 +22,7 @@ protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStac ThrowHelper.ThrowNotSupportedException_CannotPopulateCollection(TypeToConvert, ref reader, ref state); } - state.Current.ReturnValue = new Dictionary(); + state.Current.ReturnValue = new Dictionary(); } protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfTKeyTValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfTKeyTValueConverter.cs index ac9544d0528591..f2ade306400304 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfTKeyTValueConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfTKeyTValueConverter.cs @@ -19,21 +19,21 @@ protected override void Add(TKey key, in TValue value, JsonSerializerOptions opt protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStack state) { - state.Current.ReturnValue = new Dictionary(); + state.Current.ReturnValue = new Dictionary(); } protected override void ConvertCollection(ref ReadStack state, JsonSerializerOptions options) { JsonClassInfo classInfo = state.Current.JsonClassInfo; - Func>, TCollection>? creator = (Func>, TCollection>?)classInfo.CreateObjectWithArgs; + Func>, TCollection>? creator = (Func>, TCollection>?)classInfo.CreateObjectWithArgs; if (creator == null) { - creator = options.MemberAccessorStrategy.CreateImmutableDictionaryCreateRangeDelegate(); + creator = options.MemberAccessorStrategy.CreateImmutableDictionaryCreateRangeDelegate(); classInfo.CreateObjectWithArgs = creator; } - state.Current.ReturnValue = creator((Dictionary)state.Current.ReturnValue!); + state.Current.ReturnValue = creator((Dictionary)state.Current.ReturnValue!); } protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableEnumerableOfTConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableEnumerableOfTConverter.cs index 6e8d0eb4e9f41b..eeb262b8ddb7da 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableEnumerableOfTConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableEnumerableOfTConverter.cs @@ -28,7 +28,7 @@ protected override void ConvertCollection(ref ReadStack state, JsonSerializerOpt Func, TCollection>? creator = (Func, TCollection>?)classInfo.CreateObjectWithArgs; if (creator == null) { - creator = options.MemberAccessorStrategy.CreateImmutableEnumerableCreateRangeDelegate(); + creator = options.MemberAccessorStrategy.CreateImmutableEnumerableCreateRangeDelegate(); classInfo.CreateObjectWithArgs = creator; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ObjectConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ObjectConverter.cs index a9feb74b5c6c1a..c9e10f9877282a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ObjectConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ObjectConverter.cs @@ -24,7 +24,10 @@ public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOp } internal override object ReadWithQuotes(ref Utf8JsonReader reader) - => throw new NotSupportedException(); + { + ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(TypeToConvert); + return null!; + } internal override void WriteWithQuotes(Utf8JsonWriter writer, object value, JsonSerializerOptions options, ref WriteStack state) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs index badc1b45a71105..f68d64b85b4ca8 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs @@ -17,9 +17,9 @@ public abstract JsonClassInfo.ParameterizedConstructorDelegate CreateAddMethodDelegate(); - public abstract Func, TCollection> CreateImmutableEnumerableCreateRangeDelegate(); + public abstract Func, TCollection> CreateImmutableEnumerableCreateRangeDelegate(); - public abstract Func>, TCollection> CreateImmutableDictionaryCreateRangeDelegate(); + public abstract Func>, TCollection> CreateImmutableDictionaryCreateRangeDelegate(); public abstract Func CreatePropertyGetter(PropertyInfo propertyInfo); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs index 042a64a547cb63..ba5e4ac2f3374e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs @@ -171,7 +171,7 @@ private static DynamicMethod CreateAddMethodDelegate(Type collectionType) return dynamicMethod; } - public override Func, TCollection> CreateImmutableEnumerableCreateRangeDelegate() => + public override Func, TCollection> CreateImmutableEnumerableCreateRangeDelegate() => CreateDelegate, TCollection>>( CreateImmutableEnumerableCreateRangeDelegate(typeof(TCollection), typeof(TElement), typeof(IEnumerable))); @@ -195,13 +195,13 @@ private static DynamicMethod CreateImmutableEnumerableCreateRangeDelegate(Type c return dynamicMethod; } - public override Func>, TCollection> CreateImmutableDictionaryCreateRangeDelegate() => - CreateDelegate>, TCollection>>( - CreateImmutableDictionaryCreateRangeDelegate(typeof(TCollection), typeof(TElement), typeof(IEnumerable>))); + public override Func>, TCollection> CreateImmutableDictionaryCreateRangeDelegate() => + CreateDelegate>, TCollection>>( + CreateImmutableDictionaryCreateRangeDelegate(typeof(TCollection), typeof(TKey), typeof(TValue), typeof(IEnumerable>))); - private static DynamicMethod CreateImmutableDictionaryCreateRangeDelegate(Type collectionType, Type elementType, Type enumerableType) + private static DynamicMethod CreateImmutableDictionaryCreateRangeDelegate(Type collectionType, Type keyType, Type valueType, Type enumerableType) { - MethodInfo realMethod = collectionType.GetImmutableDictionaryCreateRangeMethod(elementType); + MethodInfo realMethod = collectionType.GetImmutableDictionaryCreateRangeMethod(keyType, valueType); var dynamicMethod = new DynamicMethod( realMethod.Name, diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs index bb9537d50384a8..8ca593a8828a34 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs @@ -123,18 +123,18 @@ public override JsonClassInfo.ParameterizedConstructorDelegate, TCollection> CreateImmutableEnumerableCreateRangeDelegate() + public override Func, TCollection> CreateImmutableEnumerableCreateRangeDelegate() { MethodInfo createRange = typeof(TCollection).GetImmutableEnumerableCreateRangeMethod(typeof(TElement)); return (Func, TCollection>)createRange.CreateDelegate( typeof(Func, TCollection>)); } - public override Func>, TCollection> CreateImmutableDictionaryCreateRangeDelegate() + public override Func>, TCollection> CreateImmutableDictionaryCreateRangeDelegate() { - MethodInfo createRange = typeof(TCollection).GetImmutableDictionaryCreateRangeMethod(typeof(TElement)); - return (Func>, TCollection>)createRange.CreateDelegate( - typeof(Func>, TCollection>)); + MethodInfo createRange = typeof(TCollection).GetImmutableDictionaryCreateRangeMethod(typeof(TKey), typeof(TValue)); + return (Func>, TCollection>)createRange.CreateDelegate( + typeof(Func>, TCollection>)); } public override Func CreatePropertyGetter(PropertyInfo propertyInfo) diff --git a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.NonStringKey.cs b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.NonStringKey.cs index bb57ce6dad7dd5..8070445e09d454 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.NonStringKey.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.NonStringKey.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; +using System.Collections.Immutable; using System.Collections.Specialized; using System.IO; using System.Threading.Tasks; @@ -574,5 +575,49 @@ public class SuffixNamingPolicy : JsonNamingPolicy public const string Suffix = "_Suffix"; public override string ConvertName(string name) => name + Suffix; } + + [Fact] + public static void RoundtripAllDictionaryConverters() + { + const string Expected = @"{""1"":1}"; + + foreach (Type type in CollectionTestTypes.DeserializableDictionaryTypes()) + { + object dict = JsonSerializer.Deserialize(Expected, type); + Assert.Equal(Expected, JsonSerializer.Serialize(dict, type)); + } + } + + [Theory] + [InlineData(typeof(IDictionary))] + [InlineData(typeof(Hashtable))] + public static void IDictionary_Keys_ShouldBe_String_WhenDeserializing(Type type) + { + const string Expected = @"{""1998-02-14"":1}"; + + IDictionary dict = (IDictionary)JsonSerializer.Deserialize(Expected, type); + Assert.Equal(1, dict.Count); + JsonElement element = Assert.IsType(dict["1998-02-14"]); + Assert.Equal(1, element.GetInt32()); + + Assert.Equal(Expected, JsonSerializer.Serialize(dict, type)); + } + + [Fact] + public static void GenericDictionary_WithObjectKeys_Throw_WhenDeserializing() + { + const string Expected = @"{""1998-02-14"":1}"; + + var dict = new Dictionary { ["1998-02-14"] = 1 }; + RunTest>(dict); + RunTest>(dict); + RunTest>(ImmutableDictionary.CreateRange(dict)); + + void RunTest(T dictionary) + { + Assert.Throws(() => JsonSerializer.Deserialize(Expected)); + Assert.Equal(Expected, JsonSerializer.Serialize(dictionary)); + } + } } } diff --git a/src/libraries/System.Text.Json/tests/Serialization/NumberHandlingTests.cs b/src/libraries/System.Text.Json/tests/Serialization/NumberHandlingTests.cs index e132fb0043c132..48ac13e0e6aed9 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/NumberHandlingTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/NumberHandlingTests.cs @@ -626,13 +626,13 @@ private static void RunAllDictionariessRoundTripTest(List numbers) string jsonNumbersAsStrings = jsonBuilder_NumbersAsStrings.ToString(); - foreach (Type type in CollectionTestTypes.DeserializableDictionaryTypes()) + foreach (Type type in CollectionTestTypes.DeserializableDictionaryTypes()) { object obj = JsonSerializer.Deserialize(jsonNumbersAsStrings, type, s_optionReadAndWriteFromStr); JsonTestHelper.AssertJsonEqual(jsonNumbersAsStrings, JsonSerializer.Serialize(obj, s_optionReadAndWriteFromStr)); } - foreach (Type type in CollectionTestTypes.DeserializableNonDictionaryTypes()) + foreach (Type type in CollectionTestTypes.DeserializableNonGenericDictionaryTypes()) { Dictionary dict = JsonSerializer.Deserialize>(jsonNumbersAsStrings, s_optionReadAndWriteFromStr); diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.cs index 858a376c85a115..a7251994412279 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.cs @@ -1939,17 +1939,19 @@ public static IEnumerable DictionaryTypes() yield return typeof(GenericIReadOnlyDictionaryWrapper); // IReadOnlyDictionaryOfStringTValueConverter } - public static IEnumerable DeserializableDictionaryTypes() + public static IEnumerable DeserializableDictionaryTypes() { - yield return typeof(Dictionary); // DictionaryOfStringTValueConverter + yield return typeof(Dictionary); // DictionaryOfStringTValueConverter yield return typeof(Hashtable); // IDictionaryConverter - yield return typeof(ConcurrentDictionary); // IDictionaryOfStringTValueConverter - yield return typeof(GenericIDictionaryWrapper); // IDictionaryOfStringTValueConverter - yield return typeof(ImmutableDictionary); // ImmutableDictionaryOfStringTValueConverter - yield return typeof(IReadOnlyDictionary); // IReadOnlyDictionaryOfStringTValueConverter + yield return typeof(IDictionary); // IDictionaryConverter + yield return typeof(ConcurrentDictionary); // IDictionaryOfStringTValueConverter + yield return typeof(IDictionary); // IDictionaryOfStringTValueConverter + yield return typeof(GenericIDictionaryWrapper); // IDictionaryOfStringTValueConverter + yield return typeof(ImmutableDictionary); // ImmutableDictionaryOfStringTValueConverter + yield return typeof(IReadOnlyDictionary); // IReadOnlyDictionaryOfStringTValueConverter } - public static IEnumerable DeserializableNonDictionaryTypes() + public static IEnumerable DeserializableNonGenericDictionaryTypes() { yield return typeof(Hashtable); // IDictionaryConverter yield return typeof(SortedList); // IDictionaryConverter