Align dictionary key converter/metadata retrieval with pattern for collection element...
authorLayomi Akinrinade <laakinri@microsoft.com>
Wed, 24 Mar 2021 22:54:44 +0000 (15:54 -0700)
committerGitHub <noreply@github.com>
Wed, 24 Mar 2021 22:54:44 +0000 (22:54 +0000)
* Align dictionary key converter/metadata retrieval with pattern for collection elements

* Address review feedback

16 files changed:
src/libraries/System.Text.Json/src/Resources/Strings.resx
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfTKeyTValueConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryOfTKeyTValueConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfTKeyTValueConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfTKeyTValueConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonDictionaryConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ObjectConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.NonStringKey.cs

index a82a475..217b522 100644 (file)
     <value>The JSON value is not in a supported Boolean format.</value>
   </data>
   <data name="DictionaryKeyTypeNotSupported" xml:space="preserve">
-    <value>The type '{0}' is not a supported Dictionary key type.</value>
+    <value>The type '{0}' is not a supported dictionary key using converter of type '{1}'.</value>
   </data>
   <data name="IgnoreConditionOnValueTypeInvalid" xml:space="preserve">
     <value>The ignore condition 'JsonIgnoreCondition.WhenWritingNull' is not valid on value-type member '{0}' on type '{1}'. Consider using 'JsonIgnoreCondition.WhenWritingDefault'.</value>
index 606821b..5a86ad1 100644 (file)
@@ -31,22 +31,20 @@ namespace System.Text.Json.Serialization.Converters
 
         internal override Type ElementType => typeof(TValue);
 
-        protected static readonly Type KeyType = typeof(TKey);
+        internal override Type KeyType => typeof(TKey);
+
 
         protected JsonConverter<TKey>? _keyConverter;
         protected JsonConverter<TValue>? _valueConverter;
 
-        protected static JsonConverter<TValue> GetValueConverter(JsonClassInfo elementClassInfo)
+        protected static JsonConverter<T> GetConverter<T>(JsonClassInfo classInfo)
         {
-            JsonConverter<TValue> converter = (JsonConverter<TValue>)elementClassInfo.PropertyInfoForClassInfo.ConverterBase;
+            JsonConverter<T> converter = (JsonConverter<T>)classInfo.PropertyInfoForClassInfo.ConverterBase;
             Debug.Assert(converter != null); // It should not be possible to have a null converter at this point.
 
             return converter;
         }
 
-        protected static JsonConverter<TKey> GetKeyConverter(Type keyType, JsonSerializerOptions options)
-            => (JsonConverter<TKey>)options.GetDictionaryKeyConverter(keyType);
-
         internal sealed override bool OnTryRead(
             ref Utf8JsonReader reader,
             Type typeToConvert,
@@ -67,8 +65,8 @@ namespace System.Text.Json.Serialization.Converters
 
                 CreateCollection(ref reader, ref state);
 
-                JsonConverter<TValue> valueConverter = _valueConverter ??= GetValueConverter(elementClassInfo);
-                if (valueConverter.CanUseDirectReadOrWrite && state.Current.NumberHandling == null)
+                _valueConverter ??= GetConverter<TValue>(elementClassInfo);
+                if (_valueConverter.CanUseDirectReadOrWrite && state.Current.NumberHandling == null)
                 {
                     // Process all elements.
                     while (true)
@@ -88,7 +86,7 @@ namespace System.Text.Json.Serialization.Converters
 
                         // Read the value and add.
                         reader.ReadWithVerify();
-                        TValue? element = valueConverter.Read(ref reader, ElementType, options);
+                        TValue? element = _valueConverter.Read(ref reader, ElementType, options);
                         Add(key, element!, options, ref state);
                     }
                 }
@@ -113,7 +111,7 @@ namespace System.Text.Json.Serialization.Converters
                         reader.ReadWithVerify();
 
                         // Get the value from the converter and add it.
-                        valueConverter.TryRead(ref reader, ElementType, options, ref state, out TValue? element);
+                        _valueConverter.TryRead(ref reader, ElementType, options, ref state, out TValue? element);
                         Add(key, element!, options, ref state);
                     }
                 }
@@ -160,7 +158,7 @@ namespace System.Text.Json.Serialization.Converters
                 }
 
                 // Process all elements.
-                JsonConverter<TValue> elementConverter = _valueConverter ??= GetValueConverter(elementClassInfo);
+                _valueConverter ??= GetConverter<TValue>(elementClassInfo);
                 while (true)
                 {
                     if (state.Current.PropertyState == StackFramePropertyState.None)
@@ -210,7 +208,7 @@ namespace System.Text.Json.Serialization.Converters
                     {
                         state.Current.PropertyState = StackFramePropertyState.ReadValue;
 
-                        if (!SingleValueReadWithReadAhead(elementConverter.ClassType, ref reader, ref state))
+                        if (!SingleValueReadWithReadAhead(_valueConverter.ClassType, ref reader, ref state))
                         {
                             state.Current.DictionaryKey = key;
                             value = default;
@@ -221,7 +219,7 @@ namespace System.Text.Json.Serialization.Converters
                     if (state.Current.PropertyState < StackFramePropertyState.TryRead)
                     {
                         // Get the value from the converter and add it.
-                        bool success = elementConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValue? element);
+                        bool success = _valueConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValue? element);
                         if (!success)
                         {
                             state.Current.DictionaryKey = key;
@@ -252,8 +250,8 @@ namespace System.Text.Json.Serialization.Converters
                 }
                 else
                 {
-                    JsonConverter<TKey> keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options);
-                    key = keyConverter.ReadWithQuotes(ref reader);
+                    _keyConverter ??= GetConverter<TKey>(state.Current.JsonClassInfo.KeyClassInfo!);
+                    key = _keyConverter.ReadWithQuotes(ref reader);
                     unescapedPropertyNameAsString = reader.GetString()!;
                 }
 
index 4528ddc..7f9cfe9 100644 (file)
@@ -50,19 +50,18 @@ namespace System.Text.Json.Serialization.Converters
                 enumerator = (Dictionary<TKey, TValue>.Enumerator)state.Current.CollectionEnumerator;
             }
 
-            JsonClassInfo elementClassInfo = state.Current.JsonClassInfo.ElementClassInfo!;
+            JsonClassInfo classInfo = state.Current.JsonClassInfo;
+            _keyConverter ??= GetConverter<TKey>(classInfo.KeyClassInfo!);
+            _valueConverter ??= GetConverter<TValue>(classInfo.ElementClassInfo!);
 
-            JsonConverter<TKey> keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options);
-            JsonConverter<TValue> valueConverter = _valueConverter ??= GetValueConverter(elementClassInfo);
-
-            if (!state.SupportContinuation && valueConverter.CanUseDirectReadOrWrite && state.Current.NumberHandling == null)
+            if (!state.SupportContinuation && _valueConverter.CanUseDirectReadOrWrite && state.Current.NumberHandling == null)
             {
                 // Fast path that avoids validation and extra indirection.
                 do
                 {
                     TKey key = enumerator.Current.Key;
-                    keyConverter.WriteWithQuotes(writer, key, options, ref state);
-                    valueConverter.Write(writer, enumerator.Current.Value, options);
+                    _keyConverter.WriteWithQuotes(writer, key, options, ref state);
+                    _valueConverter.Write(writer, enumerator.Current.Value, options);
                 } while (enumerator.MoveNext());
             }
             else
@@ -80,11 +79,11 @@ namespace System.Text.Json.Serialization.Converters
                         state.Current.PropertyState = StackFramePropertyState.Name;
 
                         TKey key = enumerator.Current.Key;
-                        keyConverter.WriteWithQuotes(writer, key, options, ref state);
+                        _keyConverter.WriteWithQuotes(writer, key, options, ref state);
                     }
 
                     TValue element = enumerator.Current.Value;
-                    if (!valueConverter.TryWrite(writer, element, options, ref state))
+                    if (!_valueConverter.TryWrite(writer, element, options, ref state))
                     {
                         state.Current.CollectionEnumerator = enumerator;
                         return false;
index c989e5e..96754c6 100644 (file)
@@ -24,11 +24,6 @@ namespace System.Text.Json.Serialization.Converters
             };
         }
 
-        private JsonConverter<object>? _objectConverter;
-
-        private static JsonConverter<object> GetObjectKeyConverter(JsonSerializerOptions options)
-            => (JsonConverter<object>)options.GetDictionaryKeyConverter(typeof(object));
-
         protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStack state)
         {
             JsonClassInfo classInfo = state.Current.JsonClassInfo;
@@ -77,7 +72,9 @@ namespace System.Text.Json.Serialization.Converters
                 enumerator = (IDictionaryEnumerator)state.Current.CollectionEnumerator;
             }
 
-            JsonConverter<object?> valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo.ElementClassInfo!);
+            JsonClassInfo classInfo = state.Current.JsonClassInfo;
+            _valueConverter ??= GetConverter<object?>(classInfo.ElementClassInfo!);
+
             do
             {
                 if (ShouldFlush(writer, ref state))
@@ -93,20 +90,19 @@ namespace System.Text.Json.Serialization.Converters
                     // Optimize for string since that's the hot path.
                     if (key is string keyString)
                     {
-                        JsonConverter<string> stringKeyConverter = _keyConverter ??= GetKeyConverter(KeyType, options);
-                        stringKeyConverter.WriteWithQuotes(writer, keyString, options, ref state);
+                        _keyConverter ??= GetConverter<string>(classInfo.KeyClassInfo!);
+                        _keyConverter.WriteWithQuotes(writer, keyString, options, ref state);
                     }
                     else
                     {
                         // IDictionary is a special case since it has polymorphic object semantics on serialization
                         // but needs to use JsonConverter<string> on deserialization.
-                        JsonConverter<object> objectKeyConverter = _objectConverter ??= GetObjectKeyConverter(options);
-                        objectKeyConverter.WriteWithQuotes(writer, key, options, ref state);
+                        _valueConverter.WriteWithQuotes(writer, key, options, ref state);
                     }
                 }
 
                 object? element = enumerator.Value;
-                if (!valueConverter.TryWrite(writer, element, options, ref state))
+                if (!_valueConverter.TryWrite(writer, element, options, ref state))
                 {
                     state.Current.CollectionEnumerator = enumerator;
                     return false;
index ad2d185..a16d5c3 100644 (file)
@@ -76,8 +76,10 @@ namespace System.Text.Json.Serialization.Converters
                 enumerator = (IEnumerator<KeyValuePair<TKey, TValue>>)state.Current.CollectionEnumerator;
             }
 
-            JsonConverter<TKey> keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options);
-            JsonConverter<TValue> valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo.ElementClassInfo!);
+            JsonClassInfo classInfo = state.Current.JsonClassInfo;
+            _keyConverter ??= GetConverter<TKey>(classInfo.KeyClassInfo!);
+            _valueConverter ??= GetConverter<TValue>(classInfo.ElementClassInfo!);
+
             do
             {
                 if (ShouldFlush(writer, ref state))
@@ -90,11 +92,11 @@ namespace System.Text.Json.Serialization.Converters
                 {
                     state.Current.PropertyState = StackFramePropertyState.Name;
                     TKey key = enumerator.Current.Key;
-                    keyConverter.WriteWithQuotes(writer, key, options, ref state);
+                    _keyConverter.WriteWithQuotes(writer, key, options, ref state);
                 }
 
                 TValue element = enumerator.Current.Value;
-                if (!valueConverter.TryWrite(writer, element, options, ref state))
+                if (!_valueConverter.TryWrite(writer, element, options, ref state))
                 {
                     state.Current.CollectionEnumerator = enumerator;
                     return false;
index 8a9f452..8045e1b 100644 (file)
@@ -42,8 +42,10 @@ namespace System.Text.Json.Serialization.Converters
                 enumerator = (Dictionary<TKey, TValue>.Enumerator)state.Current.CollectionEnumerator;
             }
 
-            JsonConverter<TKey> keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options);
-            JsonConverter<TValue> valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo.ElementClassInfo!);
+            JsonClassInfo classInfo = state.Current.JsonClassInfo;
+            _keyConverter ??= GetConverter<TKey>(classInfo.KeyClassInfo!);
+            _valueConverter ??= GetConverter<TValue>(classInfo.ElementClassInfo!);
+
             do
             {
                 if (ShouldFlush(writer, ref state))
@@ -57,11 +59,11 @@ namespace System.Text.Json.Serialization.Converters
                     state.Current.PropertyState = StackFramePropertyState.Name;
 
                     TKey key = enumerator.Current.Key;
-                    keyConverter.WriteWithQuotes(writer, key, options, ref state);
+                    _keyConverter.WriteWithQuotes(writer, key, options, ref state);
                 }
 
                 TValue element = enumerator.Current.Value;
-                if (!valueConverter.TryWrite(writer, element, options, ref state))
+                if (!_valueConverter.TryWrite(writer, element, options, ref state))
                 {
                     state.Current.CollectionEnumerator = enumerator;
                     return false;
index f9d73d5..009dc45 100644 (file)
@@ -53,8 +53,10 @@ namespace System.Text.Json.Serialization.Converters
                 enumerator = (IEnumerator<KeyValuePair<TKey, TValue>>)state.Current.CollectionEnumerator;
             }
 
-            JsonConverter<TKey> keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options);
-            JsonConverter<TValue> valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo.ElementClassInfo!);
+            JsonClassInfo classInfo = state.Current.JsonClassInfo;
+            _keyConverter ??= GetConverter<TKey>(classInfo.KeyClassInfo!);
+            _valueConverter ??= GetConverter<TValue>(classInfo.ElementClassInfo!);
+
             do
             {
                 if (ShouldFlush(writer, ref state))
@@ -68,11 +70,11 @@ namespace System.Text.Json.Serialization.Converters
                     state.Current.PropertyState = StackFramePropertyState.Name;
 
                     TKey key = enumerator.Current.Key;
-                    keyConverter.WriteWithQuotes(writer, key, options, ref state);
+                    _keyConverter.WriteWithQuotes(writer, key, options, ref state);
                 }
 
                 TValue element = enumerator.Current.Value;
-                if (!valueConverter.TryWrite(writer, element, options, ref state))
+                if (!_valueConverter.TryWrite(writer, element, options, ref state))
                 {
                     state.Current.CollectionEnumerator = enumerator;
                     return false;
index 6bf8227..dd45d8c 100644 (file)
@@ -9,6 +9,7 @@ namespace System.Text.Json.Serialization
     internal abstract class JsonDictionaryConverter<T> : JsonResumableConverter<T>
     {
         internal sealed override ClassType ClassType => ClassType.Dictionary;
+
         protected internal abstract bool OnWriteResume(Utf8JsonWriter writer, T dictionary, JsonSerializerOptions options, ref WriteStack state);
     }
 }
index cf29923..ee55ded 100644 (file)
@@ -22,25 +22,20 @@ namespace System.Text.Json.Serialization.Converters
 
         internal override object ReadWithQuotes(ref Utf8JsonReader reader)
         {
-            ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(TypeToConvert);
+            ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(TypeToConvert, this);
             return null!;
         }
 
         internal override void WriteWithQuotes(Utf8JsonWriter writer, object value, JsonSerializerOptions options, ref WriteStack state)
         {
-            JsonConverter runtimeConverter = GetRuntimeConverter(value.GetType(), options);
-            runtimeConverter.WriteWithQuotesAsObject(writer, value, options, ref state);
-        }
-
-        private JsonConverter GetRuntimeConverter(Type runtimeType, JsonSerializerOptions options)
-        {
-            JsonConverter runtimeConverter = options.GetDictionaryKeyConverter(runtimeType);
+            Type runtimeType = value.GetType();
+            JsonConverter runtimeConverter = options.GetConverter(runtimeType);
             if (runtimeConverter == this)
             {
-                ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(runtimeType);
+                ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(runtimeType, this);
             }
 
-            return runtimeConverter;
+            runtimeConverter.WriteWithQuotesAsObject(writer, value, options, ref state);
         }
 
         internal override object ReadNumberWithCustomHandling(ref Utf8JsonReader reader, JsonNumberHandling handling)
index 6919995..16374a3 100644 (file)
@@ -30,7 +30,7 @@ namespace System.Text.Json
 
         public JsonPropertyInfo? DataExtensionProperty { get; private set; }
 
-        // If enumerable, the JsonClassInfo for the element type.
+        // If enumerable or dictionary, the JsonClassInfo for the element type.
         private JsonClassInfo? _elementClassInfo;
 
         /// <summary>
@@ -58,6 +58,33 @@ namespace System.Text.Json
 
         public Type? ElementType { get; set; }
 
+        // If dictionary, the JsonClassInfo for the key type.
+        private JsonClassInfo? _keyClassInfo;
+
+        /// <summary>
+        /// Return the JsonClassInfo for the key type, or null if the type is not a dictionary.
+        /// </summary>
+        /// <remarks>
+        /// This should not be called during warm-up (initial creation of JsonClassInfos) to avoid recursive behavior
+        /// which could result in a StackOverflowException.
+        /// </remarks>
+        public JsonClassInfo? KeyClassInfo
+        {
+            get
+            {
+                if (_keyClassInfo == null && KeyType != null)
+                {
+                    Debug.Assert(ClassType == ClassType.Dictionary);
+
+                    _keyClassInfo = Options.GetOrAddClass(KeyType);
+                }
+
+                return _keyClassInfo;
+            }
+        }
+
+        public Type? KeyType { get; set; }
+
         public JsonSerializerOptions Options { get; private set; }
 
         public Type Type { get; private set; }
@@ -222,8 +249,14 @@ namespace System.Text.Json
                     }
                     break;
                 case ClassType.Enumerable:
+                    {
+                        ElementType = converter.ElementType;
+                        CreateObject = Options.MemberAccessorStrategy.CreateConstructor(runtimeType);
+                    }
+                    break;
                 case ClassType.Dictionary:
                     {
+                        KeyType = converter.KeyType;
                         ElementType = converter.ElementType;
                         CreateObject = Options.MemberAccessorStrategy.CreateConstructor(runtimeType);
                     }
index f8a000e..2171ad3 100644 (file)
@@ -39,6 +39,8 @@ namespace System.Text.Json.Serialization
 
         internal abstract Type? ElementType { get; }
 
+        internal abstract Type? KeyType { get; }
+
         /// <summary>
         /// Cached value of TypeToConvert.IsValueType, which is an expensive call.
         /// </summary>
index c106ba6..404ea47 100644 (file)
@@ -51,6 +51,8 @@ namespace System.Text.Json.Serialization
             throw new InvalidOperationException();
         }
 
+        internal sealed override Type? KeyType => null;
+
         internal sealed override Type? ElementType => null;
 
         internal JsonConverter GetConverterInternal(Type typeToConvert, JsonSerializerOptions options)
index c8ca15d..cfc1324 100644 (file)
@@ -64,6 +64,8 @@ namespace System.Text.Json.Serialization
             return new JsonParameterInfo<T>();
         }
 
+        internal override Type? KeyType => null;
+
         internal override Type? ElementType => null;
 
         /// <summary>
@@ -540,10 +542,13 @@ namespace System.Text.Json.Serialization
         public abstract void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options);
 
         internal virtual T ReadWithQuotes(ref Utf8JsonReader reader)
-            => throw new InvalidOperationException();
+        {
+            ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(TypeToConvert, this);
+            return default;
+        }
 
         internal virtual void WriteWithQuotes(Utf8JsonWriter writer, [DisallowNull] T value, JsonSerializerOptions options, ref WriteStack state)
-            => throw new InvalidOperationException();
+            => ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(TypeToConvert, this);
 
         internal sealed override void WriteWithQuotesAsObject(Utf8JsonWriter writer, object value, JsonSerializerOptions options, ref WriteStack state)
             => WriteWithQuotes(writer, (T)value, options, ref state);
index e442176..2f17dee 100644 (file)
@@ -74,66 +74,6 @@ namespace System.Text.Json
                 converters.Add(converter.TypeToConvert, converter);
         }
 
-        internal JsonConverter GetDictionaryKeyConverter(Type keyType)
-        {
-            _dictionaryKeyConverters ??= GetDictionaryKeyConverters();
-
-            if (!_dictionaryKeyConverters.TryGetValue(keyType, out JsonConverter? converter))
-            {
-                if (keyType.IsEnum)
-                {
-                    converter = GetEnumConverter();
-                    _dictionaryKeyConverters[keyType] = converter;
-                }
-                else
-                {
-                    ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(keyType);
-                }
-            }
-
-            return converter!;
-
-            // Use factory pattern to generate an EnumConverter with AllowStrings and AllowNumbers options for dictionary keys.
-            // There will be one converter created for each enum type.
-            JsonConverter GetEnumConverter()
-                => EnumConverterFactory.Create(keyType, EnumConverterOptions.AllowStrings | EnumConverterOptions.AllowNumbers, this);
-        }
-
-        private ConcurrentDictionary<Type, JsonConverter>? _dictionaryKeyConverters;
-
-        private static ConcurrentDictionary<Type, JsonConverter> GetDictionaryKeyConverters()
-        {
-            const int NumberOfConverters = 18;
-            var converters = new ConcurrentDictionary<Type, JsonConverter>(Environment.ProcessorCount, NumberOfConverters);
-
-            // When adding to this, update NumberOfConverters above.
-            Add(s_defaultSimpleConverters[typeof(bool)]);
-            Add(s_defaultSimpleConverters[typeof(byte)]);
-            Add(s_defaultSimpleConverters[typeof(char)]);
-            Add(s_defaultSimpleConverters[typeof(DateTime)]);
-            Add(s_defaultSimpleConverters[typeof(DateTimeOffset)]);
-            Add(s_defaultSimpleConverters[typeof(double)]);
-            Add(s_defaultSimpleConverters[typeof(decimal)]);
-            Add(s_defaultSimpleConverters[typeof(Guid)]);
-            Add(s_defaultSimpleConverters[typeof(short)]);
-            Add(s_defaultSimpleConverters[typeof(int)]);
-            Add(s_defaultSimpleConverters[typeof(long)]);
-            Add(s_defaultSimpleConverters[typeof(object)]);
-            Add(s_defaultSimpleConverters[typeof(sbyte)]);
-            Add(s_defaultSimpleConverters[typeof(float)]);
-            Add(s_defaultSimpleConverters[typeof(string)]);
-            Add(s_defaultSimpleConverters[typeof(ushort)]);
-            Add(s_defaultSimpleConverters[typeof(uint)]);
-            Add(s_defaultSimpleConverters[typeof(ulong)]);
-
-            Debug.Assert(NumberOfConverters == converters.Count);
-
-            return converters;
-
-            void Add(JsonConverter converter) =>
-                converters[converter.TypeToConvert] = converter;
-        }
-
         /// <summary>
         /// The list of custom converters.
         /// </summary>
@@ -360,6 +300,5 @@ namespace System.Text.Json
             ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateAttribute(attributeType, classType, memberInfo);
             return default;
         }
-
     }
 }
index bee153d..cee3335 100644 (file)
@@ -35,9 +35,9 @@ namespace System.Text.Json
 
         [DoesNotReturn]
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowNotSupportedException_DictionaryKeyTypeNotSupported(Type keyType)
+        public static void ThrowNotSupportedException_DictionaryKeyTypeNotSupported(Type keyType, JsonConverter converter)
         {
-            throw new NotSupportedException(SR.Format(SR.DictionaryKeyTypeNotSupported, keyType));
+            throw new NotSupportedException(SR.Format(SR.DictionaryKeyTypeNotSupported, keyType, converter.GetType()));
         }
 
         [DoesNotReturn]
index 8070445..ded5255 100644 (file)
@@ -619,5 +619,69 @@ namespace System.Text.Json.Serialization.Tests
                 Assert.Equal(Expected, JsonSerializer.Serialize(dictionary));
             }
         }
+
+        [Fact]
+        public static void KeyWithCustomConverter()
+        {
+            // TODO: update these tests after https://github.com/dotnet/runtime/issues/50071 is implemented.
+
+            JsonSerializerOptions options = new()
+            {
+                Converters = { new ConverterForInt32(), new ComplexKeyConverter() }
+            };
+
+            // Primitive key
+            string json = @"{
+    ""PrimitiveKey"":{
+        ""1"":""1""
+    }
+}
+";
+            ClassWithNonStringDictKeys obj = new()
+            {
+                PrimitiveKey = new Dictionary<int, string> { [1] = "1" },
+            };
+            RunTest(obj, json, typeof(int).ToString(), typeof(ConverterForInt32).ToString());
+
+            // Complex key
+            json = @"{
+    ""ComplexKey"":{
+        ""SomeStringRepresentation"":""1""
+    }
+}
+";
+            obj = new()
+            {
+                ComplexKey = new Dictionary<ClassWithIDictionary, string> { [new ClassWithIDictionary()] = "1" },
+            };
+            RunTest(obj, json, typeof(ClassWithIDictionary).ToString(), typeof(ComplexKeyConverter).ToString());
+
+            void RunTest(ClassWithNonStringDictKeys obj, string payload, string keyTypeAsStr, string converterTypeAsStr)
+            {
+                NotSupportedException ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(obj, options));
+                string exAsStr = ex.ToString();
+                Assert.Contains(keyTypeAsStr, exAsStr);
+                Assert.Contains(converterTypeAsStr, exAsStr);
+
+                ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithNonStringDictKeys>(payload, options));
+                exAsStr = ex.ToString();
+                Assert.Contains(keyTypeAsStr, exAsStr);
+                Assert.Contains(converterTypeAsStr, exAsStr);
+            }
+        }
+
+        private class ClassWithNonStringDictKeys
+        {
+            public Dictionary<int, string> PrimitiveKey { get; set; }
+            public Dictionary<ClassWithIDictionary, string> ComplexKey { get; set; }
+        }
+
+        private class ComplexKeyConverter : JsonConverter<ClassWithIDictionary>
+        {
+            public override ClassWithIDictionary? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+                => throw new NotImplementedException();
+            public override void Write(Utf8JsonWriter writer, ClassWithIDictionary value, JsonSerializerOptions options)
+                => throw new NotImplementedException();
+        }
     }
 }