From c0662e8129beaf93b8050d39a863cc6d16a0308c Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 11 Aug 2021 11:42:33 +0300 Subject: [PATCH] Fix regression in dictionary key serialization when registering custom primitive converters. (#57138) * Fixes regression in dictionary key serialization when registering custom primitive converters. * reinstate debug assertion * add test case for JsonTypeInfo overloads * update method names * fix sourcegen test --- .../Collection/DictionaryDefaultConverter.cs | 2 +- .../DictionaryOfTKeyTValueConverter.cs | 4 +- .../Collection/IDictionaryConverter.cs | 4 +- .../Collection/JsonDictionaryConverter.cs | 2 +- .../Converters/Value/BooleanConverter.cs | 4 +- .../Converters/Value/ByteConverter.cs | 4 +- .../Converters/Value/CharConverter.cs | 6 +- .../Converters/Value/DateTimeConverter.cs | 4 +- .../Value/DateTimeOffsetConverter.cs | 4 +- .../Converters/Value/DecimalConverter.cs | 4 +- .../Converters/Value/DoubleConverter.cs | 4 +- .../Converters/Value/EnumConverter.cs | 6 +- .../Converters/Value/GuidConverter.cs | 4 +- .../Converters/Value/Int16Converter.cs | 4 +- .../Converters/Value/Int32Converter.cs | 4 +- .../Converters/Value/Int64Converter.cs | 4 +- .../Converters/Value/ObjectConverter.cs | 6 +- .../Converters/Value/SByteConverter.cs | 4 +- .../Converters/Value/SingleConverter.cs | 4 +- .../Converters/Value/StringConverter.cs | 4 +- .../Converters/Value/UInt16Converter.cs | 4 +- .../Converters/Value/UInt32Converter.cs | 4 +- .../Converters/Value/UInt64Converter.cs | 4 +- .../Text/Json/Serialization/JsonConverter.cs | 4 +- .../Serialization/JsonConverterFactory.cs | 2 +- .../Json/Serialization/JsonConverterOfT.cs | 27 ++++- .../JsonSerializerOptions.Converters.cs | 16 ++- ...CollectionTests.Dictionary.NonStringKey.cs | 113 ++++++++++++------ 28 files changed, 161 insertions(+), 95 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs index ba836674511..45e71830542 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs @@ -55,7 +55,7 @@ internal abstract class DictionaryDefaultConverter { state.Current.PropertyState = StackFramePropertyState.Name; TKey key = enumerator.Current.Key; - _keyConverter.WriteWithQuotes(writer, key, options, ref state); + _keyConverter.WriteAsPropertyName(writer, key, options, ref state); } TValue element = enumerator.Current.Value; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfTKeyTValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfTKeyTValueConverter.cs index 2214e0ee16a..3620a683a51 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfTKeyTValueConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfTKeyTValueConverter.cs @@ -61,7 +61,7 @@ protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStac do { TKey key = enumerator.Current.Key; - _keyConverter.WriteWithQuotes(writer, key, options, ref state); + _keyConverter.WriteAsPropertyName(writer, key, options, ref state); _valueConverter.Write(writer, enumerator.Current.Value, options); } while (enumerator.MoveNext()); } @@ -80,7 +80,7 @@ protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStac state.Current.PropertyState = StackFramePropertyState.Name; TKey key = enumerator.Current.Key; - _keyConverter.WriteWithQuotes(writer, key, options, ref state); + _keyConverter.WriteAsPropertyName(writer, key, options, ref state); } TValue element = enumerator.Current.Value; 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 bd605adb12f..1d289ffa631 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 @@ -92,13 +92,13 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TDictionar if (key is string keyString) { _keyConverter ??= GetConverter(typeInfo.KeyTypeInfo!); - _keyConverter.WriteWithQuotes(writer, keyString, options, ref state); + _keyConverter.WriteAsPropertyName(writer, keyString, options, ref state); } else { // IDictionary is a special case since it has polymorphic object semantics on serialization // but needs to use JsonConverter on deserialization. - _valueConverter.WriteWithQuotes(writer, key, options, ref state); + _valueConverter.WriteAsPropertyName(writer, key, options, ref state); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonDictionaryConverter.cs index f26f713c440..5332d4e45dc 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonDictionaryConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonDictionaryConverter.cs @@ -261,7 +261,7 @@ TKey ReadDictionaryKey(ref Utf8JsonReader reader, ref ReadStack state) else { _keyConverter ??= GetConverter(state.Current.JsonTypeInfo.KeyTypeInfo!); - key = _keyConverter.ReadWithQuotes(ref reader); + key = _keyConverter.ReadAsPropertyName(ref reader, typeToConvert, options); unescapedPropertyNameAsString = reader.GetString()!; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/BooleanConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/BooleanConverter.cs index 34601954ef5..34b96144de1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/BooleanConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/BooleanConverter.cs @@ -17,7 +17,7 @@ public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOpti writer.WriteBooleanValue(value); } - internal override bool ReadWithQuotes(ref Utf8JsonReader reader) + internal override bool ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { ReadOnlySpan propertyName = reader.GetSpan(); if (Utf8Parser.TryParse(propertyName, out bool value, out int bytesConsumed) @@ -29,7 +29,7 @@ internal override bool ReadWithQuotes(ref Utf8JsonReader reader) throw ThrowHelper.GetFormatException(DataType.Boolean); } - internal override void WriteWithQuotes(Utf8JsonWriter writer, bool value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, bool value, JsonSerializerOptions options, ref WriteStack state) { writer.WritePropertyName(value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ByteConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ByteConverter.cs index 53f19619ecf..104dfb8c364 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ByteConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ByteConverter.cs @@ -20,12 +20,12 @@ public override void Write(Utf8JsonWriter writer, byte value, JsonSerializerOpti writer.WriteNumberValue(value); } - internal override byte ReadWithQuotes(ref Utf8JsonReader reader) + internal override byte ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetByteWithQuotes(); } - internal override void WriteWithQuotes(Utf8JsonWriter writer, byte value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, byte value, JsonSerializerOptions options, ref WriteStack state) { writer.WritePropertyName(value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.cs index 73365f70bdc..22d5ddfc370 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.cs @@ -28,10 +28,10 @@ public override void Write(Utf8JsonWriter writer, char value, JsonSerializerOpti ); } - internal override char ReadWithQuotes(ref Utf8JsonReader reader) - => Read(ref reader, default!, default!); + internal override char ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => Read(ref reader, typeToConvert, options); - internal override void WriteWithQuotes(Utf8JsonWriter writer, char value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, char value, JsonSerializerOptions options, ref WriteStack state) { writer.WritePropertyName( #if BUILDING_INBOX_LIBRARY diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeConverter.cs index 5cf2919d4b2..e143baaee52 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeConverter.cs @@ -15,12 +15,12 @@ public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializer writer.WriteStringValue(value); } - internal override DateTime ReadWithQuotes(ref Utf8JsonReader reader) + internal override DateTime ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetDateTimeNoValidation(); } - internal override void WriteWithQuotes(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options, ref WriteStack state) { writer.WritePropertyName(value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeOffsetConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeOffsetConverter.cs index 02eae743c63..284e35706c1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeOffsetConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeOffsetConverter.cs @@ -15,12 +15,12 @@ public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSeri writer.WriteStringValue(value); } - internal override DateTimeOffset ReadWithQuotes(ref Utf8JsonReader reader) + internal override DateTimeOffset ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetDateTimeOffsetNoValidation(); } - internal override void WriteWithQuotes(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options, ref WriteStack state) { writer.WritePropertyName(value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DecimalConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DecimalConverter.cs index 988b7cacc0c..d5881d9ba6a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DecimalConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DecimalConverter.cs @@ -20,12 +20,12 @@ public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerO writer.WriteNumberValue(value); } - internal override decimal ReadWithQuotes(ref Utf8JsonReader reader) + internal override decimal ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetDecimalWithQuotes(); } - internal override void WriteWithQuotes(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options, ref WriteStack state) { writer.WritePropertyName(value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DoubleConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DoubleConverter.cs index 85fe5f5c5f4..b62a4234811 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DoubleConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DoubleConverter.cs @@ -20,12 +20,12 @@ public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOp writer.WriteNumberValue(value); } - internal override double ReadWithQuotes(ref Utf8JsonReader reader) + internal override double ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetDoubleWithQuotes(); } - internal override void WriteWithQuotes(Utf8JsonWriter writer, double value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, double value, JsonSerializerOptions options, ref WriteStack state) { writer.WritePropertyName(value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs index b4e666bc96b..088f41c09bf 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs @@ -84,7 +84,7 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial return default; } - return ReadWithQuotes(ref reader); + return ReadAsPropertyName(ref reader, typeToConvert, options); } if (token != JsonTokenType.Number || !_converterOptions.HasFlag(EnumConverterOptions.AllowNumbers)) @@ -304,7 +304,7 @@ private string FormatEnumValueToString(string value, JavaScriptEncoder? encoder) return converted; } - internal override T ReadWithQuotes(ref Utf8JsonReader reader) + internal override T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { string? enumString = reader.GetString(); @@ -318,7 +318,7 @@ internal override T ReadWithQuotes(ref Utf8JsonReader reader) return value; } - internal override void WriteWithQuotes(Utf8JsonWriter writer, T value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options, ref WriteStack state) { // An EnumConverter that invokes this method // can only be created by JsonSerializerOptions.GetDictionaryKeyConverter diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/GuidConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/GuidConverter.cs index 718505619e7..47e02730c25 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/GuidConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/GuidConverter.cs @@ -15,12 +15,12 @@ public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOpti writer.WriteStringValue(value); } - internal override Guid ReadWithQuotes(ref Utf8JsonReader reader) + internal override Guid ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetGuidNoValidation(); } - internal override void WriteWithQuotes(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options, ref WriteStack state) { writer.WritePropertyName(value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int16Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int16Converter.cs index 6e2682c73fa..64ed4bd23bf 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int16Converter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int16Converter.cs @@ -21,12 +21,12 @@ public override void Write(Utf8JsonWriter writer, short value, JsonSerializerOpt writer.WriteNumberValue((long)value); } - internal override short ReadWithQuotes(ref Utf8JsonReader reader) + internal override short ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetInt16WithQuotes(); } - internal override void WriteWithQuotes(Utf8JsonWriter writer, short value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, short value, JsonSerializerOptions options, ref WriteStack state) { writer.WritePropertyName(value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int32Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int32Converter.cs index 63118cc193e..953cae3e636 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int32Converter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int32Converter.cs @@ -21,12 +21,12 @@ public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptio writer.WriteNumberValue((long)value); } - internal override int ReadWithQuotes(ref Utf8JsonReader reader) + internal override int ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetInt32WithQuotes(); } - internal override void WriteWithQuotes(Utf8JsonWriter writer, int value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, int value, JsonSerializerOptions options, ref WriteStack state) { writer.WritePropertyName(value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int64Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int64Converter.cs index 4824f7cb6f3..73f34118b39 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int64Converter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int64Converter.cs @@ -20,12 +20,12 @@ public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOpti writer.WriteNumberValue(value); } - internal override long ReadWithQuotes(ref Utf8JsonReader reader) + internal override long ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetInt64WithQuotes(); } - internal override void WriteWithQuotes(Utf8JsonWriter writer, long value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, long value, JsonSerializerOptions options, ref WriteStack state) { writer.WritePropertyName(value); } 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 65b47d65074..740627cfd74 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 @@ -30,13 +30,13 @@ public override void Write(Utf8JsonWriter writer, object? value, JsonSerializerO writer.WriteEndObject(); } - internal override object ReadWithQuotes(ref Utf8JsonReader reader) + internal override object ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(TypeToConvert, this); return null!; } - internal override void WriteWithQuotes(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state) { // This converter does not handle nulls. Debug.Assert(value != null); @@ -48,7 +48,7 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, object? value, Jso ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(runtimeType, this); } - runtimeConverter.WriteWithQuotesAsObject(writer, value, options, ref state); + runtimeConverter.WriteAsPropertyNameAsObject(writer, value, options, ref state); } internal override object? ReadNumberWithCustomHandling(ref Utf8JsonReader reader, JsonNumberHandling handling, JsonSerializerOptions options) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SByteConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SByteConverter.cs index c661b28546c..042ac8aedbe 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SByteConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SByteConverter.cs @@ -20,12 +20,12 @@ public override void Write(Utf8JsonWriter writer, sbyte value, JsonSerializerOpt writer.WriteNumberValue(value); } - internal override sbyte ReadWithQuotes(ref Utf8JsonReader reader) + internal override sbyte ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetSByteWithQuotes(); } - internal override void WriteWithQuotes(Utf8JsonWriter writer, sbyte value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, sbyte value, JsonSerializerOptions options, ref WriteStack state) { writer.WritePropertyName(value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SingleConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SingleConverter.cs index 1456d95de27..4ec8bc84322 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SingleConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SingleConverter.cs @@ -21,12 +21,12 @@ public override void Write(Utf8JsonWriter writer, float value, JsonSerializerOpt writer.WriteNumberValue(value); } - internal override float ReadWithQuotes(ref Utf8JsonReader reader) + internal override float ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetSingleWithQuotes(); } - internal override void WriteWithQuotes(Utf8JsonWriter writer, float value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, float value, JsonSerializerOptions options, ref WriteStack state) { writer.WritePropertyName(value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/StringConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/StringConverter.cs index 08972856b69..c611e33562d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/StringConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/StringConverter.cs @@ -23,12 +23,12 @@ public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerO } } - internal override string ReadWithQuotes(ref Utf8JsonReader reader) + internal override string ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetString()!; } - internal override void WriteWithQuotes(Utf8JsonWriter writer, string value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, string value, JsonSerializerOptions options, ref WriteStack state) { if (options.DictionaryKeyPolicy != null && !state.Current.IgnoreDictionaryKeyPolicy) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt16Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt16Converter.cs index 1c5d37cb2b9..0c9040328af 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt16Converter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt16Converter.cs @@ -21,12 +21,12 @@ public override void Write(Utf8JsonWriter writer, ushort value, JsonSerializerOp writer.WriteNumberValue((long)value); } - internal override ushort ReadWithQuotes(ref Utf8JsonReader reader) + internal override ushort ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetUInt16WithQuotes(); } - internal override void WriteWithQuotes(Utf8JsonWriter writer, ushort value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, ushort value, JsonSerializerOptions options, ref WriteStack state) { writer.WritePropertyName(value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt32Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt32Converter.cs index 79ad9d95e56..26f57e25f58 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt32Converter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt32Converter.cs @@ -21,12 +21,12 @@ public override void Write(Utf8JsonWriter writer, uint value, JsonSerializerOpti writer.WriteNumberValue((ulong)value); } - internal override uint ReadWithQuotes(ref Utf8JsonReader reader) + internal override uint ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetUInt32WithQuotes(); } - internal override void WriteWithQuotes(Utf8JsonWriter writer, uint value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, uint value, JsonSerializerOptions options, ref WriteStack state) { writer.WritePropertyName(value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt64Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt64Converter.cs index 41dcc625030..64a27bad515 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt64Converter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt64Converter.cs @@ -20,12 +20,12 @@ public override void Write(Utf8JsonWriter writer, ulong value, JsonSerializerOpt writer.WriteNumberValue(value); } - internal override ulong ReadWithQuotes(ref Utf8JsonReader reader) + internal override ulong ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetUInt64WithQuotes(); } - internal override void WriteWithQuotes(Utf8JsonWriter writer, ulong value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteAsPropertyName(Utf8JsonWriter writer, ulong value, JsonSerializerOptions options, ref WriteStack state) { writer.WritePropertyName(value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs index f1626c4289f..c326e7685fa 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs @@ -105,9 +105,9 @@ internal bool ShouldFlush(Utf8JsonWriter writer, ref WriteStack state) internal abstract bool WriteCoreAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state); /// - /// Loosely-typed WriteWithQuotes() that forwards to strongly-typed WriteWithQuotes(). + /// Loosely-typed WriteToPropertyName() that forwards to strongly-typed WriteToPropertyName(). /// - internal abstract void WriteWithQuotesAsObject(Utf8JsonWriter writer, object value, JsonSerializerOptions options, ref WriteStack state); + internal abstract void WriteAsPropertyNameAsObject(Utf8JsonWriter writer, object value, JsonSerializerOptions options, ref WriteStack state); // Whether a type (ConverterStrategy.Object) is deserialized using a parameterized constructor. internal virtual bool ConstructorIsParameterized { get; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs index 073dede4b43..2163562afb4 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs @@ -119,7 +119,7 @@ internal JsonConverter GetConverterInternal(Type typeToConvert, JsonSerializerOp throw new InvalidOperationException(); } - internal sealed override void WriteWithQuotesAsObject( + internal sealed override void WriteAsPropertyNameAsObject( Utf8JsonWriter writer, object value, JsonSerializerOptions options, ref WriteStack state) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs index 89d6c380ae9..d34b8e06db3 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs @@ -607,17 +607,34 @@ internal void VerifyWrite(int originalDepth, Utf8JsonWriter writer) #nullable restore JsonSerializerOptions options); - internal virtual T ReadWithQuotes(ref Utf8JsonReader reader) + internal virtual T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + if (!IsInternalConverter && options.TryGetDefaultSimpleConverter(TypeToConvert, out JsonConverter? defaultConverter)) + { + // .NET 5 backward compatibility: hardcode the default converter for primitive key serialization. + Debug.Assert(defaultConverter.IsInternalConverter && defaultConverter is JsonConverter); + return ((JsonConverter)defaultConverter).ReadAsPropertyName(ref reader, TypeToConvert, options); + } + ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(TypeToConvert, this); return default; } - internal virtual void WriteWithQuotes(Utf8JsonWriter writer, [DisallowNull] T value, JsonSerializerOptions options, ref WriteStack state) - => ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(TypeToConvert, this); + internal virtual void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options, ref WriteStack state) + { + if (!IsInternalConverter && options.TryGetDefaultSimpleConverter(TypeToConvert, out JsonConverter? defaultConverter)) + { + // .NET 5 backward compatibility: hardcode the default converter for primitive key serialization. + Debug.Assert(defaultConverter.IsInternalConverter && defaultConverter is JsonConverter); + ((JsonConverter)defaultConverter).WriteAsPropertyName(writer, value, options, ref state); + return; + } + + 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); + internal sealed override void WriteAsPropertyNameAsObject(Utf8JsonWriter writer, object value, JsonSerializerOptions options, ref WriteStack state) + => WriteAsPropertyName(writer, (T)value, options, ref state); internal virtual T ReadNumberWithCustomHandling(ref Utf8JsonReader reader, JsonNumberHandling handling, JsonSerializerOptions options) => throw new InvalidOperationException(); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs index e394d9a91e7..e6259827fd5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs @@ -224,7 +224,6 @@ internal JsonConverter GetConverterInternal(Type typeToConvert) if (s_defaultSimpleConverters.TryGetValue(typeToConvert, out JsonConverter? foundConverter)) { - Debug.Assert(foundConverter != null); converter = foundConverter; } else @@ -318,6 +317,21 @@ private JsonConverter GetConverterFromAttribute(JsonConverterAttribute converter return converter; } + internal bool TryGetDefaultSimpleConverter(Type typeToConvert, [NotNullWhen(true)] out JsonConverter? converter) + { + if (_context == null && // For consistency do not return any default converters for + // options instances linked to a JsonSerializerContext, + // even if the default converters might have been rooted. + s_defaultSimpleConverters != null && + s_defaultSimpleConverters.TryGetValue(typeToConvert, out converter)) + { + return true; + } + + converter = null; + return false; + } + private static Attribute? GetAttributeThatCanHaveMultiple(Type classType, Type attributeType, MemberInfo memberInfo) { object[] attributes = memberInfo.GetCustomAttributes(attributeType, inherit: false); diff --git a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Dictionary.NonStringKey.cs b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Dictionary.NonStringKey.cs index ded52553dd3..177b42fae56 100644 --- a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Dictionary.NonStringKey.cs +++ b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Dictionary.NonStringKey.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Collections.Specialized; using System.IO; +using System.Text.Json.Serialization.Metadata; using System.Threading.Tasks; using Xunit; @@ -621,59 +622,93 @@ void RunTest(T dictionary) } [Fact] - public static void KeyWithCustomConverter() + public static void KeyWithCustomPrimitiveConverter_FallbackToDefaultConverter() { - // TODO: update these tests after https://github.com/dotnet/runtime/issues/50071 is implemented. + // Validates .NET 5 primitive custom key converter behavior. JsonSerializerOptions options = new() { - Converters = { new ConverterForInt32(), new ComplexKeyConverter() } + Converters = { new ConverterForInt32() } }; - // Primitive key - string json = @"{ - ""PrimitiveKey"":{ - ""1"":""1"" - } -} -"; - ClassWithNonStringDictKeys obj = new() - { - PrimitiveKey = new Dictionary { [1] = "1" }, - }; - RunTest(obj, json, typeof(int).ToString(), typeof(ConverterForInt32).ToString()); + var dictionary = new Dictionary { [1] = "1" }; - // Complex key - json = @"{ - ""ComplexKey"":{ - ""SomeStringRepresentation"":""1"" - } -} -"; - obj = new() + string expectedJson = @"{""1"":""1""}"; + string actualJson = JsonSerializer.Serialize(dictionary, options); + Assert.Equal(expectedJson, actualJson); + + dictionary = JsonSerializer.Deserialize>(expectedJson); + Assert.True(dictionary.ContainsKey(1)); + } + + [Fact] + public static void KeyWithCustomPrimitiveConverter_JsonTypeInfo_ThrowsNotSupportedException() + { + JsonSerializer.Serialize(42); // Ensure default converters are rooted in current process + + CustomInt32ConverterSerializerContext ctx = new(); + + var dictionary = new Dictionary { [1] = "1" }; + NotSupportedException ex = Assert.Throws(() => JsonSerializer.Serialize(dictionary, ctx.DictionaryInt32String)); + ValidateException(ex); + + string json = @"{""1"":""1""}"; + ex = Assert.Throws(() => JsonSerializer.Deserialize>(json, ctx.DictionaryInt32String)); + ValidateException(ex); + + static void ValidateException(NotSupportedException ex) { - ComplexKey = new Dictionary { [new ClassWithIDictionary()] = "1" }, - }; - RunTest(obj, json, typeof(ClassWithIDictionary).ToString(), typeof(ComplexKeyConverter).ToString()); + Assert.Contains(nameof(Int32), ex.Message); + Assert.Contains(nameof(ConverterForInt32), ex.Message); + } + } + + public class CustomInt32ConverterSerializerContext : JsonSerializerContext + { + public CustomInt32ConverterSerializerContext() : base(null, null) { } + public override JsonTypeInfo? GetTypeInfo(Type _) => throw new NotImplementedException(); - void RunTest(ClassWithNonStringDictKeys obj, string payload, string keyTypeAsStr, string converterTypeAsStr) + public JsonTypeInfo> DictionaryInt32String => _dictionaryInt32String ??= CreateDictionaryConverter(); + private JsonTypeInfo>? _dictionaryInt32String; + + private JsonTypeInfo> CreateDictionaryConverter() { - NotSupportedException ex = Assert.Throws(() => JsonSerializer.Serialize(obj, options)); - string exAsStr = ex.ToString(); - Assert.Contains(keyTypeAsStr, exAsStr); - Assert.Contains(converterTypeAsStr, exAsStr); - - ex = Assert.Throws(() => JsonSerializer.Deserialize(payload, options)); - exAsStr = ex.ToString(); - Assert.Contains(keyTypeAsStr, exAsStr); - Assert.Contains(converterTypeAsStr, exAsStr); + JsonTypeInfo keyInfo = JsonMetadataServices.CreateValueInfo(Options, new ConverterForInt32()); + JsonTypeInfo valueInfo = JsonMetadataServices.CreateValueInfo(Options, JsonMetadataServices.StringConverter); + return JsonMetadataServices.CreateDictionaryInfo, int, string>( + Options, + createObjectFunc: () => new(), + keyInfo, valueInfo, + numberHandling: default, + serializeFunc: null + ); } } - private class ClassWithNonStringDictKeys + [Fact] + public static void KeyWithCustomClassConverter_ThrowsNotSupportedException() { - public Dictionary PrimitiveKey { get; set; } - public Dictionary ComplexKey { get; set; } + // TODO: update after https://github.com/dotnet/runtime/issues/46520 is implemented. + + JsonSerializerOptions options = new() + { + Converters = { new ComplexKeyConverter() } + }; + + var dictionary = new Dictionary { [new ClassWithIDictionary()] = "1" }; + + NotSupportedException ex = Assert.Throws(() => JsonSerializer.Serialize(dictionary, options)); + ValidateException(ex); + + string json = @"{""SomeStringRepresentation"":""1""}"; + ex = Assert.Throws(() => JsonSerializer.Deserialize>(json, options)); + ValidateException(ex); + + static void ValidateException(NotSupportedException ex) + { + Assert.Contains(nameof(ClassWithIDictionary), ex.Message); + Assert.Contains(nameof(ComplexKeyConverter), ex.Message); + } } private class ComplexKeyConverter : JsonConverter -- GitLab