From f222f234af1e9fadbb3f3182e9a1011bbf122987 Mon Sep 17 00:00:00 2001 From: David Cantu Date: Wed, 17 Jun 2020 15:33:31 -0700 Subject: [PATCH 01/16] Add support for multiple non-string TKey on dictionaries --- .../src/System.Text.Json.csproj | 8 +- .../Collection/DictionaryDefaultConverter.cs | 67 +-- ....cs => DictionaryOfTKeyTValueConverter.cs} | 26 +- .../Collection/IDictionaryConverter.cs | 23 +- ...cs => IDictionaryOfTKeyTValueConverter.cs} | 21 +- .../Collection/IEnumerableConverterFactory.cs | 79 ++-- ...eadOnlyDictionaryOfTKeyTValueConverter.cs} | 24 +- ...mutableDictionaryOfTKeyTValueConverter.cs} | 22 +- .../Collection/JsonCollectionConverter.cs | 1 + .../Converters/Object/JsonObjectConverter.cs | 1 + .../Converters/Value/EnumConverter.cs | 22 + .../Converters/Value/GuidConverter.cs | 26 ++ .../Converters/Value/Int32Converter.cs | 26 ++ .../Converters/Value/ObjectConverter.cs | 26 ++ .../Converters/Value/StringConverter.cs | 22 + .../Text/Json/Serialization/JsonClassInfo.cs | 26 +- .../Text/Json/Serialization/JsonConverter.cs | 10 + .../Serialization/JsonConverterFactory.cs | 2 + .../Json/Serialization/JsonConverterOfT.cs | 13 +- .../Text/Json/Serialization/ReadStackFrame.cs | 1 + ...CollectionTests.Dictionary.NonStringKey.cs | 430 ++++++++++++++++++ .../CollectionTests.Dictionary.cs | 23 +- .../CollectionTests.Generic.Read.cs | 6 +- .../CustomConverterTests.DerivedTypes.cs | 4 +- .../tests/Serialization/ExtensionDataTests.cs | 3 +- .../Serialization/PropertyVisibilityTests.cs | 24 +- .../tests/System.Text.Json.Tests.csproj | 3 +- 27 files changed, 766 insertions(+), 173 deletions(-) rename src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/{DictionaryOfStringTValueConverter.cs => DictionaryOfTKeyTValueConverter.cs} (75%) rename src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/{IDictionaryOfStringTValueConverter.cs => IDictionaryOfTKeyTValueConverter.cs} (81%) rename src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/{IReadOnlyDictionaryOfStringTValueConverter.cs => IReadOnlyDictionaryOfTKeyTValueConverter.cs} (71%) rename src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/{ImmutableDictionaryOfStringTValueConverter.cs => ImmutableDictionaryOfTKeyTValueConverter.cs} (75%) create mode 100644 src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.NonStringKey.cs 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 0908515412735..c34cdda18ee27 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -65,10 +65,10 @@ - + - + @@ -77,9 +77,9 @@ - + - + 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 77c8ff882159e..4dc43aa2a075e 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 @@ -10,13 +10,14 @@ namespace System.Text.Json.Serialization.Converters /// /// Default base class implementation of JsonDictionaryConverter{TCollection} . /// - internal abstract class DictionaryDefaultConverter + internal abstract class DictionaryDefaultConverter : JsonDictionaryConverter + where TKey : notnull { /// /// When overridden, adds the value to the collection. /// - protected abstract void Add(TValue value, JsonSerializerOptions options, ref ReadStack state); + protected abstract void Add(TKey key, TValue value, JsonSerializerOptions options, ref ReadStack state); /// /// When overridden, converts the temporary collection held in state.Current.ReturnValue to the final collection. @@ -30,6 +31,7 @@ protected virtual void ConvertCollection(ref ReadStack state, JsonSerializerOpti protected virtual void CreateCollection(ref Utf8JsonReader reader, ref ReadStack state) { } internal override Type ElementType => typeof(TValue); + internal override Type KeyType => typeof(TKey); protected static JsonConverter GetElementConverter(ref ReadStack state) { @@ -39,25 +41,21 @@ protected static JsonConverter GetElementConverter(ref ReadStack state) return converter; } - protected string GetKeyName(string key, ref WriteStack state, JsonSerializerOptions options) + protected static JsonConverter GetValueConverter(ref WriteStack state) { - if (options.DictionaryKeyPolicy != null && !state.Current.IgnoreDictionaryKeyPolicy) - { - key = options.DictionaryKeyPolicy.ConvertName(key); - - if (key == null) - { - ThrowHelper.ThrowInvalidOperationException_NamingPolicyReturnNull(options.DictionaryKeyPolicy); - } - } + JsonConverter converter = (JsonConverter)state.Current.JsonClassInfo.ElementClassInfo!.PropertyInfoForClassInfo.ConverterBase; + Debug.Assert(converter != null); // It should not be possible to have a null converter at this point. - return key; + return converter; } - protected static JsonConverter GetValueConverter(ref WriteStack state) + protected static JsonConverter GetKeyConverter(JsonClassInfo classInfo) { - JsonConverter converter = (JsonConverter)state.Current.DeclaredJsonPropertyInfo!.ConverterBase; - Debug.Assert(converter != null); // It should not be possible to have a null converter at this point. + var converter = (JsonConverter)classInfo.KeyClassInfo!.PropertyInfoForClassInfo.ConverterBase; + if (!converter.CanBeDictionaryKey) + { + ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(converter.TypeToConvert); + } return converter; } @@ -97,12 +95,17 @@ internal sealed override bool OnTryRead( // Read method would have thrown if otherwise. Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); - state.Current.JsonPropertyNameAsString = reader.GetString(); + ReadOnlySpan propertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options); + // Copy key name to JsonPropertyName for JSON Path support in case of error. + state.Current.JsonPropertyName = propertyName.ToArray(); + + JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); + TKey key = keyConverter.ReadWithQuotes(propertyName); // Read the value and add. reader.ReadWithVerify(); TValue element = elementConverter.Read(ref reader, typeof(TValue), options); - Add(element!, options, ref state); + Add(key, element!, options, ref state); } } else @@ -121,13 +124,18 @@ internal sealed override bool OnTryRead( // Read method would have thrown if otherwise. Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); - state.Current.JsonPropertyNameAsString = reader.GetString(); + ReadOnlySpan propertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options); + // Copy key name to JsonPropertyName for JSON Path support in case of error. + state.Current.JsonPropertyName = propertyName.ToArray(); + + JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); + TKey key = keyConverter.ReadWithQuotes(propertyName); reader.ReadWithVerify(); // Get the value from the converter and add it. elementConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValue element); - Add(element!, options, ref state); + Add(key, element!, options, ref state); } } } @@ -212,17 +220,10 @@ internal sealed override bool OnTryRead( state.Current.PropertyState = StackFramePropertyState.Name; - // Verify property doesn't contain metadata. - if (preserveReferences) - { - ReadOnlySpan propertyName = reader.GetSpan(); - if (propertyName.Length > 0 && propertyName[0] == '$') - { - ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state); - } - } - - state.Current.JsonPropertyNameAsString = reader.GetString(); + ReadOnlySpan propertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options); + state.Current.DictionaryKeyName = propertyName.ToArray(); + // Copy key name to JsonPropertyName for JSON Path support in case of error. + state.Current.JsonPropertyName = state.Current.DictionaryKeyName; } if (state.Current.PropertyState < StackFramePropertyState.ReadValue) @@ -238,6 +239,8 @@ internal sealed override bool OnTryRead( if (state.Current.PropertyState < StackFramePropertyState.TryRead) { + JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); + TKey key = keyConverter.ReadWithQuotes(state.Current.DictionaryKeyName); // Get the value from the converter and add it. bool success = elementConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValue element); if (!success) @@ -246,7 +249,7 @@ internal sealed override bool OnTryRead( return false; } - Add(element!, options, ref state); + Add(key, element!, options, ref state); state.Current.EndElement(); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfStringTValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfTKeyTValueConverter.cs similarity index 75% rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfStringTValueConverter.cs rename to src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfTKeyTValueConverter.cs index 14ecb4526999c..6b26227d70a70 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfStringTValueConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfTKeyTValueConverter.cs @@ -10,13 +10,14 @@ namespace System.Text.Json.Serialization.Converters /// Converter for Dictionary{string, TValue} that (de)serializes as a JSON object with properties /// representing the dictionary element key and value. /// - internal sealed class DictionaryOfStringTValueConverter - : DictionaryDefaultConverter - where TCollection : Dictionary + internal sealed class DictionaryOfTKeyTValueConverter + : DictionaryDefaultConverter + where TCollection : Dictionary + where TKey : notnull { - protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(TKey key, TValue value, JsonSerializerOptions options, ref ReadStack state) { - string key = state.Current.JsonPropertyNameAsString!; + //string key = state.Current.JsonPropertyNameAsString!; ((TCollection)state.Current.ReturnValue!)[key] = value; } @@ -36,7 +37,7 @@ protected internal override bool OnWriteResume( JsonSerializerOptions options, ref WriteStack state) { - Dictionary.Enumerator enumerator; + Dictionary.Enumerator enumerator; if (state.Current.CollectionEnumerator == null) { enumerator = value.GetEnumerator(); @@ -47,17 +48,19 @@ protected internal override bool OnWriteResume( } else { - enumerator = (Dictionary.Enumerator)state.Current.CollectionEnumerator; + enumerator = (Dictionary.Enumerator)state.Current.CollectionEnumerator; } + JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); JsonConverter converter = GetValueConverter(ref state); if (!state.SupportContinuation && converter.CanUseDirectReadOrWrite) { // Fast path that avoids validation and extra indirection. do { - string key = GetKeyName(enumerator.Current.Key, ref state, options); - writer.WritePropertyName(key); + TKey key = enumerator.Current.Key; + keyConverter.WriteWithQuotes(writer, key, options, ref state); + converter.Write(writer, enumerator.Current.Value, options); } while (enumerator.MoveNext()); } @@ -74,8 +77,9 @@ protected internal override bool OnWriteResume( if (state.Current.PropertyState < StackFramePropertyState.Name) { state.Current.PropertyState = StackFramePropertyState.Name; - string key = GetKeyName(enumerator.Current.Key, ref state, options); - writer.WritePropertyName(key); + + TKey key = enumerator.Current.Key; + keyConverter.WriteWithQuotes(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 6ef56753427f4..10f1d68a58718 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 @@ -12,12 +12,11 @@ namespace System.Text.Json.Serialization.Converters /// representing the dictionary element key and value. /// internal sealed class IDictionaryConverter - : DictionaryDefaultConverter + : DictionaryDefaultConverter where TCollection : IDictionary { - protected override void Add(object? value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(string key, object? value, JsonSerializerOptions options, ref ReadStack state) { - string key = state.Current.JsonPropertyNameAsString!; ((IDictionary)state.Current.ReturnValue!)[key] = value; } @@ -68,6 +67,12 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio enumerator = (IDictionaryEnumerator)state.Current.CollectionEnumerator; } + // Avoid using GetKeyConverter here + // IDictionary is a spacial case since it has polymorphic object semantics on serialization. + // But needs to use JsonConverter on deserialization. + JsonClassInfo objectKeyClassInfo = options.GetOrAddClass(typeof(object)); + JsonConverter keyConverter = (JsonConverter)objectKeyClassInfo.PropertyInfoForClassInfo.ConverterBase; + JsonConverter converter = GetValueConverter(ref state); do { @@ -80,16 +85,8 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio if (state.Current.PropertyState < StackFramePropertyState.Name) { state.Current.PropertyState = StackFramePropertyState.Name; - - if (enumerator.Key is string key) - { - key = GetKeyName(key, ref state, options); - writer.WritePropertyName(key); - } - else - { - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.DeclaredJsonPropertyInfo!.RuntimePropertyType!); - } + object key = enumerator.Key; + keyConverter.WriteWithQuotes(writer, key, options, ref state); } object? element = enumerator.Value; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryOfStringTValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryOfTKeyTValueConverter.cs similarity index 81% rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryOfStringTValueConverter.cs rename to src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryOfTKeyTValueConverter.cs index 6115fada07dec..6677d74ee380a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryOfStringTValueConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryOfTKeyTValueConverter.cs @@ -10,13 +10,13 @@ namespace System.Text.Json.Serialization.Converters /// Converter for System.Collections.Generic.IDictionary{string, TValue} that /// (de)serializes as a JSON object with properties representing the dictionary element key and value. /// - internal sealed class IDictionaryOfStringTValueConverter - : DictionaryDefaultConverter - where TCollection : IDictionary + internal sealed class IDictionaryOfTKeyTValueConverter + : DictionaryDefaultConverter + where TCollection : IDictionary + where TKey : notnull { - protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(TKey key, TValue value, JsonSerializerOptions options, ref ReadStack state) { - string key = state.Current.JsonPropertyNameAsString!; ((TCollection)state.Current.ReturnValue!)[key] = value; } @@ -57,7 +57,7 @@ protected internal override bool OnWriteResume( JsonSerializerOptions options, ref WriteStack state) { - IEnumerator> enumerator; + IEnumerator> enumerator; if (state.Current.CollectionEnumerator == null) { enumerator = value.GetEnumerator(); @@ -68,9 +68,10 @@ protected internal override bool OnWriteResume( } else { - enumerator = (IEnumerator>)state.Current.CollectionEnumerator; + enumerator = (IEnumerator>)state.Current.CollectionEnumerator; } + JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); JsonConverter converter = GetValueConverter(ref state); do { @@ -83,8 +84,8 @@ protected internal override bool OnWriteResume( if (state.Current.PropertyState < StackFramePropertyState.Name) { state.Current.PropertyState = StackFramePropertyState.Name; - string key = GetKeyName(enumerator.Current.Key, ref state, options); - writer.WritePropertyName(key); + TKey key = enumerator.Current.Key; + keyConverter.WriteWithQuotes(writer, key, options, ref state); } TValue element = enumerator.Current.Value; @@ -106,7 +107,7 @@ internal override Type RuntimeType { if (TypeToConvert.IsAbstract || TypeToConvert.IsInterface) { - return typeof(Dictionary); + return typeof(Dictionary); } return TypeToConvert; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs index 56012689bc5e6..bdf6c19d5fae4 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs @@ -28,25 +28,26 @@ public override bool CanConvert(Type typeToConvert) [DynamicDependency("#ctor", typeof(ArrayConverter<,>))] [DynamicDependency("#ctor", typeof(ConcurrentQueueOfTConverter<,>))] [DynamicDependency("#ctor", typeof(ConcurrentStackOfTConverter<,>))] - [DynamicDependency("#ctor", typeof(DictionaryOfStringTValueConverter<,>))] + [DynamicDependency("#ctor", typeof(DictionaryOfTKeyTValueConverter<,>))] [DynamicDependency("#ctor", typeof(ICollectionOfTConverter<,>))] - [DynamicDependency("#ctor", typeof(IDictionaryOfStringTValueConverter<,>))] + [DynamicDependency("#ctor", typeof(IDictionaryOfTKeyTValueConverter<,>))] [DynamicDependency("#ctor", typeof(IEnumerableOfTConverter<,>))] [DynamicDependency("#ctor", typeof(IEnumerableWithAddMethodConverter<>))] [DynamicDependency("#ctor", typeof(IListConverter<>))] [DynamicDependency("#ctor", typeof(IListOfTConverter<,>))] - [DynamicDependency("#ctor", typeof(ImmutableDictionaryOfStringTValueConverter<,>))] + [DynamicDependency("#ctor", typeof(ImmutableDictionaryOfTKeyTValueConverter<,>))] [DynamicDependency("#ctor", typeof(ImmutableEnumerableOfTConverter<,>))] - [DynamicDependency("#ctor", typeof(IReadOnlyDictionaryOfStringTValueConverter<,>))] + [DynamicDependency("#ctor", typeof(IReadOnlyDictionaryOfTKeyTValueConverter<,>))] [DynamicDependency("#ctor", typeof(ISetOfTConverter<,>))] [DynamicDependency("#ctor", typeof(ListOfTConverter<,>))] [DynamicDependency("#ctor", typeof(QueueOfTConverter<,>))] [DynamicDependency("#ctor", typeof(StackOfTConverter<,>))] public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) { - Type converterType = null!; + Type converterType; Type[] genericArgs; Type? elementType = null; + Type? dictionaryKeyType = null; Type? actualTypeToConvert; // Array @@ -67,61 +68,37 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer converterType = typeof(ListOfTConverter<,>); elementType = actualTypeToConvert.GetGenericArguments()[0]; } - // Dictionary or deriving from Dictionary + // Dictionary or deriving from Dictionary else if ((actualTypeToConvert = typeToConvert.GetCompatibleGenericBaseClass(typeof(Dictionary<,>))) != null) { genericArgs = actualTypeToConvert.GetGenericArguments(); - if (genericArgs[0] == typeof(string)) - { - converterType = typeof(DictionaryOfStringTValueConverter<,>); - elementType = genericArgs[1]; - } - else - { - ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(typeToConvert); - } + converterType = typeof(DictionaryOfTKeyTValueConverter<,,>); + dictionaryKeyType = genericArgs[0]; + elementType = genericArgs[1]; } - // Immutable dictionaries from System.Collections.Immutable, e.g. ImmutableDictionary + // Immutable dictionaries from System.Collections.Immutable, e.g. ImmutableDictionary else if (typeToConvert.IsImmutableDictionaryType()) { genericArgs = typeToConvert.GetGenericArguments(); - if (genericArgs[0] == typeof(string)) - { - converterType = typeof(ImmutableDictionaryOfStringTValueConverter<,>); - elementType = genericArgs[1]; - } - else - { - ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(typeToConvert); - } + converterType = typeof(ImmutableDictionaryOfTKeyTValueConverter<,,>); + dictionaryKeyType = genericArgs[0]; + elementType = genericArgs[1]; } - // IDictionary or deriving from IDictionary + // IDictionary or deriving from IDictionary else if ((actualTypeToConvert = typeToConvert.GetCompatibleGenericInterface(typeof(IDictionary<,>))) != null) { genericArgs = actualTypeToConvert.GetGenericArguments(); - if (genericArgs[0] == typeof(string)) - { - converterType = typeof(IDictionaryOfStringTValueConverter<,>); - elementType = genericArgs[1]; - } - else - { - ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(typeToConvert); - } + converterType = typeof(IDictionaryOfTKeyTValueConverter<,,>); + dictionaryKeyType = genericArgs[0]; + elementType = genericArgs[1]; } - // IReadOnlyDictionary or deriving from IReadOnlyDictionary + // IReadOnlyDictionary or deriving from IReadOnlyDictionary else if ((actualTypeToConvert = typeToConvert.GetCompatibleGenericInterface(typeof(IReadOnlyDictionary<,>))) != null) { genericArgs = actualTypeToConvert.GetGenericArguments(); - if (genericArgs[0] == typeof(string)) - { - converterType = typeof(IReadOnlyDictionaryOfStringTValueConverter<,>); - elementType = genericArgs[1]; - } - else - { - ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(typeToConvert); - } + converterType = typeof(IReadOnlyDictionaryOfTKeyTValueConverter<,,>); + dictionaryKeyType = genericArgs[0]; + elementType = genericArgs[1]; } // Immutable non-dictionaries from System.Collections.Immutable, e.g. ImmutableStack else if (typeToConvert.IsImmutableEnumerableType()) @@ -211,17 +188,21 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer converterType = typeof(IEnumerableConverter<>); } - Debug.Assert(converterType != null); - Type genericType; - if (converterType.GetGenericArguments().Length == 1) + int numberOfGenericArgs = converterType.GetGenericArguments().Length; + if (numberOfGenericArgs == 1) { genericType = converterType.MakeGenericType(typeToConvert); } - else + else if (numberOfGenericArgs == 2) { genericType = converterType.MakeGenericType(typeToConvert, elementType!); } + else + { + Debug.Assert(numberOfGenericArgs == 3); + genericType = converterType.MakeGenericType(typeToConvert, dictionaryKeyType!, elementType!); + } JsonConverter converter = (JsonConverter)Activator.CreateInstance( genericType, diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfStringTValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfTKeyTValueConverter.cs similarity index 71% rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfStringTValueConverter.cs rename to src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfTKeyTValueConverter.cs index a4552cba9a271..d61d4279259d1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfStringTValueConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfTKeyTValueConverter.cs @@ -6,14 +6,14 @@ namespace System.Text.Json.Serialization.Converters { - internal sealed class IReadOnlyDictionaryOfStringTValueConverter - : DictionaryDefaultConverter - where TCollection : IReadOnlyDictionary + internal sealed class IReadOnlyDictionaryOfTKeyTValueConverter + : DictionaryDefaultConverter + where TCollection : IReadOnlyDictionary + where TKey : notnull { - protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(TKey key, TValue value, JsonSerializerOptions options, ref ReadStack state) { - string key = state.Current.JsonPropertyNameAsString!; - ((Dictionary)state.Current.ReturnValue!)[key] = value; + ((Dictionary)state.Current.ReturnValue!)[key] = value; } protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStack state) @@ -28,7 +28,7 @@ protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStac protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state) { - IEnumerator> enumerator; + IEnumerator> enumerator; if (state.Current.CollectionEnumerator == null) { enumerator = value.GetEnumerator(); @@ -39,9 +39,10 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio } else { - enumerator = (Dictionary.Enumerator)state.Current.CollectionEnumerator; + enumerator = (Dictionary.Enumerator)state.Current.CollectionEnumerator; } + JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); JsonConverter converter = GetValueConverter(ref state); do { @@ -54,8 +55,9 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio if (state.Current.PropertyState < StackFramePropertyState.Name) { state.Current.PropertyState = StackFramePropertyState.Name; - string key = GetKeyName(enumerator.Current.Key, ref state, options); - writer.WritePropertyName(key); + + TKey key = enumerator.Current.Key; + keyConverter.WriteWithQuotes(writer, key, options, ref state); } TValue element = enumerator.Current.Value; @@ -71,6 +73,6 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio return true; } - internal override Type RuntimeType => typeof(Dictionary); + internal override Type RuntimeType => typeof(Dictionary); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfStringTValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfTKeyTValueConverter.cs similarity index 75% rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfStringTValueConverter.cs rename to src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfTKeyTValueConverter.cs index 9cde8e4233f28..4e0b2e1af90a2 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfStringTValueConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfTKeyTValueConverter.cs @@ -6,14 +6,14 @@ namespace System.Text.Json.Serialization.Converters { - internal sealed class ImmutableDictionaryOfStringTValueConverter - : DictionaryDefaultConverter - where TCollection : IReadOnlyDictionary + internal sealed class ImmutableDictionaryOfTKeyTValueConverter + : DictionaryDefaultConverter + where TCollection : IReadOnlyDictionary + where TKey : notnull { - protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(TKey key, TValue value, JsonSerializerOptions options, ref ReadStack state) { - string key = state.Current.JsonPropertyNameAsString!; - ((Dictionary)state.Current.ReturnValue!)[key] = value; + ((Dictionary)state.Current.ReturnValue!)[key] = value; } internal override bool CanHaveIdMetadata => false; @@ -39,7 +39,7 @@ protected override void ConvertCollection(ref ReadStack state, JsonSerializerOpt protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state) { - IEnumerator> enumerator; + IEnumerator> enumerator; if (state.Current.CollectionEnumerator == null) { enumerator = value.GetEnumerator(); @@ -50,9 +50,10 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio } else { - enumerator = (IEnumerator>)state.Current.CollectionEnumerator; + enumerator = (IEnumerator>)state.Current.CollectionEnumerator; } + JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); JsonConverter converter = GetValueConverter(ref state); do { @@ -65,8 +66,9 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio if (state.Current.PropertyState < StackFramePropertyState.Name) { state.Current.PropertyState = StackFramePropertyState.Name; - string key = GetKeyName(enumerator.Current.Key, ref state, options); - writer.WritePropertyName(key); + + TKey key = enumerator.Current.Key; + keyConverter.WriteWithQuotes(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/JsonCollectionConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonCollectionConverter.cs index a8c51b8cc7b01..8194f9bd86ab0 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonCollectionConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonCollectionConverter.cs @@ -11,5 +11,6 @@ internal abstract class JsonCollectionConverter : JsonRes { internal sealed override ClassType ClassType => ClassType.Enumerable; internal override Type ElementType => typeof(TElement); + internal sealed override Type? KeyType => null; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/JsonObjectConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/JsonObjectConverter.cs index 433cb307704af..73f0248992a9d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/JsonObjectConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/JsonObjectConverter.cs @@ -12,5 +12,6 @@ internal abstract class JsonObjectConverter : JsonResumableConverter { internal sealed override ClassType ClassType => ClassType.Object; internal sealed override Type? ElementType => null; + internal sealed override Type? KeyType => null; } } 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 41f7302f7db55..5485caab39339 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 @@ -317,5 +317,27 @@ private string FormatEnumValueToString(string value, JavaScriptEncoder? encoder) return converted; } + + internal override T ReadWithQuotes(ReadOnlySpan utf8Bytes) + { + string enumString = JsonReaderHelper.TranscodeHelper(utf8Bytes); + + if (!Enum.TryParse(enumString, out T value) + && !Enum.TryParse(enumString, ignoreCase: true, out value)) + { + ThrowHelper.ThrowJsonException(); + return default; + } + + return value; + } + + internal override void WriteWithQuotes(Utf8JsonWriter writer, T value, JsonSerializerOptions options, ref WriteStack state) + { + string enumString = value.ToString(); + writer.WritePropertyName(enumString); + } + + internal override bool CanBeDictionaryKey => true; } } 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 3c7c010098ffd..eca00900f1705 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 @@ -2,6 +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.Buffers.Text; +using System.Diagnostics; + namespace System.Text.Json.Serialization.Converters { internal sealed class GuidConverter : JsonConverter @@ -15,5 +18,28 @@ public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOpti { writer.WriteStringValue(value); } + + internal override Guid ReadWithQuotes(ReadOnlySpan span) + { + if (Utf8Parser.TryParse(span, out Guid value, out int bytesConsumed) + && span.Length == bytesConsumed) + { + return value; + } + + throw ThrowHelper.GetFormatException(DataType.Guid); + } + + internal override void WriteWithQuotes(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options, ref WriteStack state) + { + Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatGuidLength]; + + bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten); + Debug.Assert(result); + + writer.WritePropertyName(utf8PropertyName); + } + + internal override bool CanBeDictionaryKey => true; } } 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 abd9089c65873..66819a36b30b2 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 @@ -2,6 +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.Buffers.Text; +using System.Diagnostics; + namespace System.Text.Json.Serialization.Converters { internal sealed class Int32Converter : JsonConverter @@ -15,5 +18,28 @@ public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptio { writer.WriteNumberValue(value); } + + internal override int ReadWithQuotes(ReadOnlySpan span) + { + if (Utf8Parser.TryParse(span, out int value, out int bytesConsumed) + && span.Length == bytesConsumed) + { + return value; + } + + throw ThrowHelper.GetFormatException(NumericType.Int32); + } + + internal override void WriteWithQuotes(Utf8JsonWriter writer, int value, JsonSerializerOptions options, ref WriteStack state) + { + Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatInt64Length]; + + bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten); + Debug.Assert(result); + + writer.WritePropertyName(utf8PropertyName.Slice(0, bytesWritten)); + } + + internal override bool CanBeDictionaryKey => true; } } 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 acbae39e3edc3..099edb7607c5f 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 @@ -2,6 +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.Diagnostics.CodeAnalysis; + namespace System.Text.Json.Serialization.Converters { internal sealed class ObjectConverter : JsonConverter @@ -18,5 +20,29 @@ public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOp { throw new InvalidOperationException(); } + + internal override object ReadWithQuotes(ReadOnlySpan utf8Bytes) + => throw new NotSupportedException(); + + internal override void WriteWithQuotes(Utf8JsonWriter writer, object value, JsonSerializerOptions options, ref WriteStack state) + { + JsonConverter runtimeConverter = GetRuntimeConverter(value.GetType(), state.Current.JsonClassInfo.Type, options); + runtimeConverter.WriteWithQuotesAsObject(writer, value, options, ref state); + } + + private JsonConverter GetRuntimeConverter(Type runtimeType, Type parentType, JsonSerializerOptions options) + { + JsonConverter runtimeConverter = options.GetOrAddClass(runtimeType).PropertyInfoForClassInfo.ConverterBase; + + // We don't support object itself as TKey, only the other supported types when they are boxed. + if (runtimeConverter is JsonConverter || !runtimeConverter.CanBeDictionaryKey) + { + ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(parentType); + } + + return runtimeConverter; + } + + internal override bool CanBeDictionaryKey => true; } } 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 bd420a35e719c..91ff9f7cdf247 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 @@ -2,6 +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.Diagnostics.CodeAnalysis; + namespace System.Text.Json.Serialization.Converters { internal sealed class StringConverter : JsonConverter @@ -15,5 +17,25 @@ public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerO { writer.WriteStringValue(value); } + + internal override string ReadWithQuotes(ReadOnlySpan utf8Bytes) + => JsonReaderHelper.TranscodeHelper(utf8Bytes); + + internal override void WriteWithQuotes(Utf8JsonWriter writer, [DisallowNull] string? value, JsonSerializerOptions options, ref WriteStack state) + { + if (options.DictionaryKeyPolicy != null && !state.Current.IgnoreDictionaryKeyPolicy) + { + value = options.DictionaryKeyPolicy.ConvertName(value); + + if (value == null) + { + ThrowHelper.ThrowInvalidOperationException_NamingPolicyReturnNull(options.DictionaryKeyPolicy); + } + } + + writer.WritePropertyName(value); + } + + internal override bool CanBeDictionaryKey => true; } } 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 c4143532ce347..dd27ff7547a47 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 @@ -29,6 +29,9 @@ internal sealed partial class JsonClassInfo // If enumerable, the JsonClassInfo for the element type. private JsonClassInfo? _elementClassInfo; + // If dictionary, the JsonClassInfo for TKey. + private JsonClassInfo? _keyClassInfo; + /// /// Return the JsonClassInfo for the element type, or null if the type is not an enumerable or dictionary. /// @@ -52,8 +55,25 @@ public JsonClassInfo? ElementClassInfo } } + public JsonClassInfo? KeyClassInfo + { + get + { + if (_keyClassInfo == null && KeyType != null) + { + Debug.Assert(ClassType == ClassType.Dictionary); + + _keyClassInfo = Options.GetOrAddClass(KeyType); + } + + return _keyClassInfo; + } + } + public Type? ElementType { get; set; } + public Type? KeyType { get; set; } + public JsonSerializerOptions Options { get; private set; } public Type Type { get; private set; } @@ -203,8 +223,12 @@ public JsonClassInfo(Type type, JsonSerializerOptions options) PropertyCache = cache; } break; - case ClassType.Enumerable: case ClassType.Dictionary: + { + KeyType = converter.KeyType; + goto case ClassType.Enumerable; + } + case ClassType.Enumerable: { ElementType = converter.ElementType; CreateObject = options.MemberAccessorStrategy.CreateConstructor(runtimeType); 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 cc73208798ab4..a223653de5b53 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 @@ -32,6 +32,8 @@ internal JsonConverter() { } /// internal virtual bool CanHaveIdMetadata => true; + internal abstract bool CanBeDictionaryKey { get; } + internal bool CanBePolymorphic { get; set; } internal abstract JsonPropertyInfo CreateJsonPropertyInfo(); @@ -40,6 +42,8 @@ internal JsonConverter() { } internal abstract Type? ElementType { get; } + internal abstract Type? KeyType { get; } + /// /// Cached value of TypeToConvert.IsValueType, which is an expensive call. /// @@ -71,6 +75,12 @@ 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(). + /// + internal virtual void WriteWithQuotesAsObject(Utf8JsonWriter writer, object value, JsonSerializerOptions options, ref WriteStack state) + => throw new InvalidOperationException(); + // Whether a type (ClassType.Object) is deserialized using a parameterized constructor. internal virtual bool ConstructorIsParameterized => false; 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 5186002fe4077..a9ca08b4a1157 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 @@ -53,6 +53,8 @@ internal override JsonParameterInfo CreateJsonParameterInfo() } internal sealed override Type? ElementType => null; + internal sealed override Type? KeyType => null; + internal sealed override bool CanBeDictionaryKey => false; internal JsonConverter GetConverterInternal(Type typeToConvert, JsonSerializerOptions options) { 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 47dcc37d9b78c..25f8a10a91ad3 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 @@ -56,6 +56,8 @@ internal override sealed JsonParameterInfo CreateJsonParameterInfo() } internal override Type? ElementType => null; + internal override Type? KeyType => null; + internal override bool CanBeDictionaryKey => false; /// /// Indicates whether should be passed to the converter on serialization, @@ -338,8 +340,6 @@ internal bool TryWriteDataExtensionProperty(Utf8JsonWriter writer, T value, Json Debug.Assert(this is JsonDictionaryConverter); - state.Current.PolymorphicJsonPropertyInfo = state.Current.DeclaredJsonPropertyInfo!.RuntimeClassInfo.ElementClassInfo!.PropertyInfoForClassInfo; - if (writer.CurrentDepth >= options.EffectiveMaxDepth) { ThrowHelper.ThrowJsonException_SerializerCycleDetected(options.EffectiveMaxDepth); @@ -436,5 +436,14 @@ internal void VerifyWrite(int originalDepth, Utf8JsonWriter writer) /// The value to convert. /// The being used. public abstract void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options); + + internal virtual T ReadWithQuotes(ReadOnlySpan utf8Bytes) + => throw new InvalidOperationException(); + + internal virtual void WriteWithQuotes(Utf8JsonWriter writer, [DisallowNull] T value, JsonSerializerOptions options, ref WriteStack state) + => throw new InvalidOperationException(); + + internal sealed override void WriteWithQuotesAsObject(Utf8JsonWriter writer, object value, JsonSerializerOptions options, ref WriteStack state) + => WriteWithQuotes(writer, (T)value, options, ref state); } } 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 d9a59477faa2b..e09174a332b5f 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 @@ -17,6 +17,7 @@ internal struct ReadStackFrame // Support JSON Path on exceptions. public byte[]? JsonPropertyName; // This is Utf8 since we don't want to convert to string until an exception is thown. + public byte[]? DictionaryKeyName; // This is Utf8 since we don't want to convert to TKey until we have both key and value when parsing the dictionary elements on stream cases. public string? JsonPropertyNameAsString; // This is used for dictionary keys and re-entry cases that specify a property name. // Validation state. 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 new file mode 100644 index 0000000000000..3f43980a55c44 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.NonStringKey.cs @@ -0,0 +1,430 @@ +// 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.Collections.Specialized; +using System.IO; +using System.Threading.Tasks; +using Xunit; + +namespace System.Text.Json.Serialization.Tests +{ + public class DictionaryKeyTests + { + public abstract class DictionaryKeyTestsBase + { + protected abstract TKey Key { get; } + protected abstract TValue Value { get; } + protected virtual string _expectedJson => $"{{\"{Key}\":{Value}}}"; + + protected virtual void Validate(Dictionary dictionary) + { + bool success = dictionary.TryGetValue(Key, out TValue value); + Assert.True(success); + Assert.Equal(Value, value); + } + + private Dictionary BuildDictionary() + { + var dictionary = new Dictionary(); + dictionary.Add(Key, Value); + + return dictionary; + } + + [Fact] + public void TestDictinaryKey() + { + Dictionary dictionary = BuildDictionary(); + + string json = JsonSerializer.Serialize(dictionary); + Assert.Equal(_expectedJson, json); + + Dictionary dictionaryCopy = JsonSerializer.Deserialize>(json); + Validate(dictionaryCopy); + } + + [Fact] + public async Task TestDictionaryKeyAsync() + { + Dictionary dictionary = BuildDictionary(); + + MemoryStream serializeStream = new MemoryStream(); + await JsonSerializer.SerializeAsync(serializeStream, dictionary); + string json = Encoding.UTF8.GetString(serializeStream.ToArray()); + Assert.Equal(_expectedJson, json); + + byte[] jsonBytes = Encoding.UTF8.GetBytes(json); + Stream deserializeStream = new MemoryStream(jsonBytes); + Dictionary dictionaryCopy = await JsonSerializer.DeserializeAsync>(deserializeStream); + Validate(dictionaryCopy); + } + } + + public class DictionaryIntKey : DictionaryKeyTestsBase + { + protected override int Key => 1; + protected override int Value => 1; + } + + public class DictionaryGuidKey : DictionaryKeyTestsBase + { + // Use singleton pattern here so the Guid key does not change everytime this is called. + protected override Guid Key { get; } = Guid.NewGuid(); + protected override int Value => 1; + } + + public class DictionaryEnumKey : DictionaryKeyTestsBase + { + protected override MyEnum Key => MyEnum.Foo; + protected override int Value => 1; + } + + public class DictionaryEnumFlagsKey : DictionaryKeyTestsBase + { + protected override MyEnumFlags Key => MyEnumFlags.Foo | MyEnumFlags.Bar; + protected override int Value => 1; + } + + public class DictionaryStringKey : DictionaryKeyTestsBase + { + protected override string Key => "KeyString"; + protected override int Value => 1; + } + + public abstract class DictionaryUnsupportedKeyTestsBase + { + private Dictionary _dictionary => BuildDictionary(); + protected abstract TKey Key { get; } + private Dictionary BuildDictionary() + { + return new Dictionary() { { Key, default } }; + } + + [Fact] + public void ThrowUnsupported_Serialize() + => Assert.Throws(() => JsonSerializer.Serialize(_dictionary)); + + [Fact] + public Task ThrowUnsupported_SerializeAsync() + => Assert.ThrowsAsync(() => JsonSerializer.SerializeAsync(new MemoryStream(), _dictionary)); + + [Fact] + public void ThrowUnsupported_Deserialize() => Assert.Throws(() + => JsonSerializer.Deserialize>(@"{""foo"":1}")); + + [Fact] + public Task ThrowUnsupported_DeserializeAsync() => Assert.ThrowsAsync(() + => JsonSerializer.DeserializeAsync>(new MemoryStream(Encoding.UTF8.GetBytes(@"{""foo"":1}"))).AsTask()); + + [Fact] + public void DoesNotThrowIfEmpty_Serialize() + => JsonSerializer.Serialize(new Dictionary()); + + [Fact] + public Task DoesNotThrowIfEmpty_SerializeAsync() + => JsonSerializer.SerializeAsync(new MemoryStream(), new Dictionary()); + + [Fact] + public void DoesNotThrowIfEmpty_Deserialize() + => JsonSerializer.Deserialize>("{}"); + + [Fact] + public Task DoesNotThrowIfEmpty_DeserializeAsync() + => JsonSerializer.DeserializeAsync>(new MemoryStream(Encoding.UTF8.GetBytes("{}"))).AsTask(); + } + + public class DictionaryMyClassKeyUnsupported : DictionaryUnsupportedKeyTestsBase + { + protected override MyClass Key => new MyClass(); + } + + public class DictionaryMyStructKeyUnsupported : DictionaryUnsupportedKeyTestsBase + { + protected override MyStruct Key => new MyStruct(); + } + + public class DictionaryUriKeyUnsupported : DictionaryUnsupportedKeyTestsBase + { + protected override Uri Key => new Uri("http://foo"); + } + + public class DictionaryObjectKeyUnsupported : DictionaryUnsupportedKeyTestsBase + { + protected override object Key => new object(); + } + + public class DictionaryPolymorphicKeyUnsupported : DictionaryUnsupportedKeyTestsBase + { + protected override object Key => new Uri("http://foo"); + } + + public class DictionaryNonStringKeyTests + { + [Fact] + public void TestGenericDictionaryKeyObject() + { + var dictionary = new Dictionary(); + // Add multiple supported types. + dictionary.Add(1, 1); + dictionary.Add(new Guid("08314FA2-B1FE-4792-BCD1-6E62338AC7F3"), 2); + dictionary.Add("KeyString", 3); + dictionary.Add(MyEnum.Foo, 4); + dictionary.Add(MyEnumFlags.Foo | MyEnumFlags.Bar, 5); + + const string expected = @"{""1"":1,""08314fa2-b1fe-4792-bcd1-6e62338ac7f3"":2,""KeyString"":3,""Foo"":4,""Foo, Bar"":5}"; + + string json = JsonSerializer.Serialize(dictionary); + Assert.Equal(expected, json); + // object type is not supported on deserialization. + Assert.Throws(() => JsonSerializer.Deserialize>(json)); + + var @object = new ClassWithDictionary { Dictionary = dictionary }; + json = JsonSerializer.Serialize(@object); + Assert.Equal($@"{{""Dictionary"":{expected}}}", json); + Assert.Throws(() => JsonSerializer.Deserialize(json)); + } + + [Fact] + public void TestNonGenericDictionaryKeyObject() + { + IDictionary dictionary = new OrderedDictionary(); + // Add multiple supported types. + dictionary.Add(1, 1); + dictionary.Add(new Guid("08314FA2-B1FE-4792-BCD1-6E62338AC7F3"), 2); + dictionary.Add("KeyString", 3); + dictionary.Add(MyEnum.Foo, 4); + dictionary.Add(MyEnumFlags.Foo | MyEnumFlags.Bar, 5); + + const string expected = @"{""1"":1,""08314fa2-b1fe-4792-bcd1-6e62338ac7f3"":2,""KeyString"":3,""Foo"":4,""Foo, Bar"":5}"; + string json = JsonSerializer.Serialize(dictionary); + Assert.Equal(expected, json); + + dictionary = JsonSerializer.Deserialize(json); + Assert.IsType>(dictionary); + + dictionary = JsonSerializer.Deserialize(json); + foreach (object key in dictionary.Keys) + { + Assert.IsType(key); + } + + var @object = new ClassWithIDictionary { Dictionary = dictionary }; + json = JsonSerializer.Serialize(@object); + Assert.Equal($@"{{""Dictionary"":{expected}}}", json); + + @object = JsonSerializer.Deserialize(json); + Assert.IsType>(@object.Dictionary); + } + + [Theory] // Extend this test when support for more types is added. + [InlineData(@"{""1.1"":1}", typeof(Dictionary))] + [InlineData(@"{""{00000000-0000-0000-0000-000000000000}"":1}", typeof(Dictionary))] + public void ThrowOnInvalidFormat(string json, Type typeToConvert) + { + JsonException ex = Assert.Throws(() => JsonSerializer.Deserialize(json, typeToConvert)); + Assert.Contains(typeToConvert.ToString(), ex.Message); + } + + [Theory] // Extend this test when support for more types is added. + [InlineData(@"{""1.1"":1}", typeof(Dictionary))] + [InlineData(@"{""{00000000-0000-0000-0000-000000000000}"":1}", typeof(Dictionary))] + public async Task ThrowOnInvalidFormatAsync(string json, Type typeToConvert) + { + byte[] jsonBytes = Encoding.UTF8.GetBytes(json); + Stream stream = new MemoryStream(jsonBytes); + + JsonException ex = await Assert.ThrowsAsync(async () => await JsonSerializer.DeserializeAsync(stream, typeToConvert)); + Assert.Contains(typeToConvert.ToString(), ex.Message); + } + + [Theory] + [InlineData(@"{""\u0039"":1}", typeof(Dictionary), 9)] + [InlineData(@"{""\u0041"":1}", typeof(Dictionary), "A")] + [InlineData(@"{""\u0066\u006f\u006f"":1}", typeof(Dictionary), MyEnum.Foo)] + [InlineData(@"{""\u0066\u006f\u006f\u002c\u0020\u0062\u0061\u0072"":1}", typeof(Dictionary), MyEnumFlags.Foo | MyEnumFlags.Bar)] + public static void TestUnescapedKeys(string json, Type typeToConvert, object keyAsType) + { + object result = JsonSerializer.Deserialize(json, typeToConvert); + + IDictionary dictionary = (IDictionary)result; + Assert.True(dictionary.Contains(keyAsType)); + } + + [Fact] + public static void TestUnescapedGuidKey() + { + // Test Guid which cannot be passed as parameter on above method. + Dictionary result = JsonSerializer.Deserialize>(@"{""\u0036bb67e4e-9780-4895-851b-75f72ac34c5a"":1}"); + + Guid myGuid = new Guid("6bb67e4e-9780-4895-851b-75f72ac34c5a"); + Assert.Equal(1, result[myGuid]); + } + + [Theory] + [InlineData(@"{""\u0039"":1}", typeof(Dictionary), 9)] + [InlineData(@"{""\u0041"":1}", typeof(Dictionary), "A")] + [InlineData(@"{""\u0066\u006f\u006f"":1}", typeof(Dictionary), MyEnum.Foo)] + [InlineData(@"{""\u0066\u006f\u006f\u002c\u0020\u0062\u0061\u0072"":1}", typeof(Dictionary), MyEnumFlags.Foo | MyEnumFlags.Bar)] + public static async Task TestUnescapedKeysAsync(string json, Type typeToConvert, object keyAsType) + { + byte[] utf8Json = Encoding.UTF8.GetBytes(json); + MemoryStream stream = new MemoryStream(utf8Json); + + object result = await JsonSerializer.DeserializeAsync(stream, typeToConvert); + + IDictionary dictionary = (IDictionary)result; + Assert.True(dictionary.Contains(keyAsType)); + } + + [Fact] + public static async Task TestUnescapedGuidKeyAsync() + { + // Test Guid which cannot be passed as parameter on above method. + byte[] utf8Json = Encoding.UTF8.GetBytes(@"{""\u0036bb67e4e-9780-4895-851b-75f72ac34c5a"":1}"); + MemoryStream stream = new MemoryStream(utf8Json); + + Dictionary result = await JsonSerializer.DeserializeAsync>(stream); + + Guid myGuid = new Guid("6bb67e4e-9780-4895-851b-75f72ac34c5a"); + Assert.Equal(1, result[myGuid]); + } + + [Fact] + public static void TestNotSuportedExceptionIsThrown() + { + // Dictionary> + Assert.Null(JsonSerializer.Deserialize>("null")); + Assert.Throws(() => JsonSerializer.Deserialize>("\"\"")); + Assert.NotNull(JsonSerializer.Deserialize>("{}")); + + Assert.Throws(() => JsonSerializer.Deserialize>(@"{""Foo"":1}")); + + // UnsupportedDictionaryWrapper + Assert.Throws(() => JsonSerializer.Deserialize("\"\"")); + Assert.NotNull(JsonSerializer.Deserialize("{}")); + Assert.Null(JsonSerializer.Deserialize("null")); + Assert.NotNull(JsonSerializer.Deserialize(@"{""Dictionary"":null}")); + Assert.NotNull(JsonSerializer.Deserialize(@"{""Dictionary"":{}}")); + + Assert.Throws(() => JsonSerializer.Deserialize(@"{""Dictionary"":{""Foo"":1}}")); + } + + [Fact] + public void TestPolicyOnlyAppliesToString() + { + var opts = new JsonSerializerOptions + { + DictionaryKeyPolicy = new FixedNamingPolicy() + }; + + var stringIntDictionary = new Dictionary { { "1", 1 } }; + string json = JsonSerializer.Serialize(stringIntDictionary, opts); + Assert.Equal($@"{{""{FixedNamingPolicy.FixedName}"":1}}", json); + + var intIntDictionary = new Dictionary { { 1, 1 } }; + json = JsonSerializer.Serialize(intIntDictionary, opts); + Assert.Equal(@"{""1"":1}", json); + + var objectIntDictionary = new Dictionary { { "1", 1 } }; + json = JsonSerializer.Serialize(objectIntDictionary, opts); + Assert.Equal($@"{{""{FixedNamingPolicy.FixedName}"":1}}", json); + + objectIntDictionary = new Dictionary { { 1, 1 } }; + json = JsonSerializer.Serialize(objectIntDictionary, opts); + Assert.Equal(@"{""1"":1}", json); + } + + [Fact] + public async Task TestPolicyOnlyAppliesToStringAsync() + { + var opts = new JsonSerializerOptions + { + DictionaryKeyPolicy = new FixedNamingPolicy() + }; + + MemoryStream stream = new MemoryStream(); + + var stringIntDictionary = new Dictionary { { "1", 1 } }; + await JsonSerializer.SerializeAsync(stream, stringIntDictionary, opts); + + string json = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal($@"{{""{FixedNamingPolicy.FixedName}"":1}}", json); + + stream.Position = 0; + stream.SetLength(0); + + var intIntDictionary = new Dictionary { { 1, 1 } }; + await JsonSerializer.SerializeAsync(stream, intIntDictionary, opts); + + json = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal(@"{""1"":1}", json); + + stream.Position = 0; + stream.SetLength(0); + + var objectIntDictionary = new Dictionary { { "1", 1 } }; + await JsonSerializer.SerializeAsync(stream, objectIntDictionary, opts); + + json = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal($@"{{""{FixedNamingPolicy.FixedName}"":1}}", json); + + stream.Position = 0; + stream.SetLength(0); + + objectIntDictionary = new Dictionary { { 1, 1 } }; + await JsonSerializer.SerializeAsync(stream, objectIntDictionary, opts); + + json = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal(@"{""1"":1}", json); + } + } + + public class MyClass { } + + public struct MyStruct { } + + public enum MyEnum + { + Foo, + Bar + } + + [Flags] + public enum MyEnumFlags + { + Foo = 1, + Bar = 2, + Baz = 4 + } + + private class ClassWithIDictionary + { + public IDictionary Dictionary { get; set; } + } + + private class ClassWithDictionary + { + public Dictionary Dictionary { get; set; } + } + + private class ClassWithExtensionData + { + [JsonExtensionData] + public Dictionary Overflow { get; set; } + } + + private class UnsupportedDictionaryWrapper + { + public Dictionary Dictionary { get; set; } + } + + public class FixedNamingPolicy : JsonNamingPolicy + { + public const string FixedName = nameof(FixedName); + public override string ConvertName(string name) => FixedName; + } + } +} diff --git a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.cs b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.cs index bf1fb5a1587aa..9dd3c4d4a2fcb 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.cs @@ -641,13 +641,6 @@ public class Poco public int Id { get; set; } } - [Fact] - public static void FirstGenericArgNotStringFail() - { - Assert.Throws(() => JsonSerializer.Deserialize>(@"{1:1}")); - Assert.Throws(() => JsonSerializer.Deserialize>(@"{1:1}")); - } - [Fact] public static void DictionaryOfList() { @@ -1619,8 +1612,7 @@ public static void DictionaryNotSupported() NotSupportedException ex = Assert.Throws(() => JsonSerializer.Deserialize(json)); // The exception contains the type. - Assert.Contains(typeof(Dictionary).ToString(), ex.Message); - Assert.DoesNotContain("Path: ", ex.Message); + Assert.Contains(typeof(Dictionary).ToString(), ex.Message); } [Fact] @@ -1840,12 +1832,12 @@ public static void NullDictionaryValuesShouldDeserializeAsNull() public class ClassWithNotSupportedDictionary { - public Dictionary MyDictionary { get; set; } + public Dictionary MyDictionary { get; set; } } public class ClassWithNotSupportedDictionaryButIgnored { - [JsonIgnore] public Dictionary MyDictionary { get; set; } + [JsonIgnore] public Dictionary MyDictionary { get; set; } } public class AllSingleUpperPropertiesParent @@ -2143,15 +2135,6 @@ public static void VerifyDictionaryThatHasIncomatibleEnumeratorWithPoco() Assert.Throws(() => JsonSerializer.Serialize(dictionary)); } - - [Fact] - public static void VerifyIDictionaryWithNonStringKey() - { - IDictionary dictionary = new Hashtable(); - dictionary.Add(1, "value"); - Assert.Throws(() => JsonSerializer.Serialize(dictionary)); - } - private class ClassWithoutParameterlessCtor { public ClassWithoutParameterlessCtor(int num) { } diff --git a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Generic.Read.cs b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Generic.Read.cs index 3e9eae828e837..70340547c6b63 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Generic.Read.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Generic.Read.cs @@ -1148,10 +1148,10 @@ private static IEnumerable CustomInterfaces_Dictionaries() } [Fact] - public static void IReadOnlyDictionary_NonStringKey_NotSupported() + public static void IReadOnlyDictionary_NotSupportedKey() { - Assert.Throws(() => JsonSerializer.Deserialize>("")); - Assert.Throws(() => JsonSerializer.Serialize(new GenericIReadOnlyDictionaryWrapper())); + Assert.Throws(() => JsonSerializer.Deserialize>(@"{""http://foo"":1}")); + Assert.Throws(() => JsonSerializer.Serialize(new GenericIReadOnlyDictionaryWrapper(new Dictionary { { new Uri("http://foo"), 1 } }))); } } } diff --git a/src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests/CustomConverterTests.DerivedTypes.cs b/src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests/CustomConverterTests.DerivedTypes.cs index 37b2765cb2b2e..b97696d5facf4 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests/CustomConverterTests.DerivedTypes.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests/CustomConverterTests.DerivedTypes.cs @@ -76,7 +76,7 @@ public static void CustomUnsupportedDictionaryConverter() { DictionaryWrapper = new UnsupportedDictionaryWrapper() }; - wrapper.DictionaryWrapper[1] = 1; + wrapper.DictionaryWrapper[new int[,] { }] = 1; // Without converter, we throw. Assert.Throws(() => JsonSerializer.Deserialize(json)); @@ -128,7 +128,7 @@ public class ListWrapper : List { } public class DictionaryWrapper : Dictionary { } - public class UnsupportedDictionaryWrapper : Dictionary { } + public class UnsupportedDictionaryWrapper : Dictionary { } public class DerivedTypesWrapper { diff --git a/src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs b/src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs index 8c95c471e509e..73906d8b745c3 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs @@ -866,9 +866,8 @@ public static void ExtensionProperty_InvalidDictionary() ClassWithInvalidExtensionPropertyStringString obj1 = new ClassWithInvalidExtensionPropertyStringString(); Assert.Throws(() => JsonSerializer.Serialize(obj1)); - // This fails with NotSupportedException since all Dictionaries currently need to have a string TKey. ClassWithInvalidExtensionPropertyObjectString obj2 = new ClassWithInvalidExtensionPropertyObjectString(); - Assert.Throws(() => JsonSerializer.Serialize(obj2)); + Assert.Throws(() => JsonSerializer.Serialize(obj2)); } private class ClassWithExtensionPropertyAlreadyInstantiated diff --git a/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs index c2fd2148b3284..a5f4840c5231a 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs @@ -851,7 +851,27 @@ public static void JsonIgnoreAttribute_UnsupportedCollection() JsonSerializerOptions options = new JsonSerializerOptions(); // Unsupported collections will throw on serialize by default. - Assert.Throws(() => JsonSerializer.Serialize(new ClassWithUnsupportedDictionary(), options)); + // Only if they contain elements. + List> elements = new List>(); + // Uri is an unsupported dictionary key. + elements.Add(new KeyValuePair(new Uri("http://foo"), "bar")); + + var concurrent = new ConcurrentDictionary(elements); + var dictionary = new Dictionary(elements); + + var instance = new ClassWithUnsupportedDictionary() + { + MyConcurrentDict = concurrent, + MyIDict = dictionary + }; + + var instanceWithIgnore = new ClassWithIgnoredUnsupportedDictionary + { + MyConcurrentDict = concurrent, + MyIDict = dictionary + }; + + Assert.Throws(() => JsonSerializer.Serialize(instance, options)); // Unsupported collections will throw on deserialize by default. options = new JsonSerializerOptions(); @@ -859,7 +879,7 @@ public static void JsonIgnoreAttribute_UnsupportedCollection() options = new JsonSerializerOptions(); // Unsupported collections will throw on serialize by default. - Assert.Throws(() => JsonSerializer.Serialize(new WrapperForClassWithUnsupportedDictionary(), options)); + Assert.Throws(() => JsonSerializer.Serialize(instance, options)); // When ignored, we can serialize and deserialize without exceptions. options = new JsonSerializerOptions(); 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 ddfccacb6f8a5..918787c3201ec 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 @@ -1,4 +1,4 @@ - + $(NetCoreAppCurrent);$(NetFrameworkCurrent) true @@ -41,6 +41,7 @@ + From 25e654b948464932d783114d0906d9f01ff2f98f Mon Sep 17 00:00:00 2001 From: David Cantu Date: Wed, 17 Jun 2020 16:49:02 -0700 Subject: [PATCH 02/16] Fix build errors --- .../Converters/Collection/IEnumerableConverterFactory.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs index bdf6c19d5fae4..ee0536c612c65 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs @@ -28,16 +28,16 @@ public override bool CanConvert(Type typeToConvert) [DynamicDependency("#ctor", typeof(ArrayConverter<,>))] [DynamicDependency("#ctor", typeof(ConcurrentQueueOfTConverter<,>))] [DynamicDependency("#ctor", typeof(ConcurrentStackOfTConverter<,>))] - [DynamicDependency("#ctor", typeof(DictionaryOfTKeyTValueConverter<,>))] + [DynamicDependency("#ctor", typeof(DictionaryOfTKeyTValueConverter<,,>))] [DynamicDependency("#ctor", typeof(ICollectionOfTConverter<,>))] - [DynamicDependency("#ctor", typeof(IDictionaryOfTKeyTValueConverter<,>))] + [DynamicDependency("#ctor", typeof(IDictionaryOfTKeyTValueConverter<,,>))] [DynamicDependency("#ctor", typeof(IEnumerableOfTConverter<,>))] [DynamicDependency("#ctor", typeof(IEnumerableWithAddMethodConverter<>))] [DynamicDependency("#ctor", typeof(IListConverter<>))] [DynamicDependency("#ctor", typeof(IListOfTConverter<,>))] - [DynamicDependency("#ctor", typeof(ImmutableDictionaryOfTKeyTValueConverter<,>))] + [DynamicDependency("#ctor", typeof(ImmutableDictionaryOfTKeyTValueConverter<,,>))] [DynamicDependency("#ctor", typeof(ImmutableEnumerableOfTConverter<,>))] - [DynamicDependency("#ctor", typeof(IReadOnlyDictionaryOfTKeyTValueConverter<,>))] + [DynamicDependency("#ctor", typeof(IReadOnlyDictionaryOfTKeyTValueConverter<,,>))] [DynamicDependency("#ctor", typeof(ISetOfTConverter<,>))] [DynamicDependency("#ctor", typeof(ListOfTConverter<,>))] [DynamicDependency("#ctor", typeof(QueueOfTConverter<,>))] From 77b7a4a88b19d38356e2b0c41696d60a26a1c2e3 Mon Sep 17 00:00:00 2001 From: David Cantu Date: Thu, 18 Jun 2020 22:58:39 -0700 Subject: [PATCH 03/16] Fix performance regression --- .../Collection/DictionaryDefaultConverter.cs | 32 +++++++++++++------ .../Collection/IDictionaryConverter.cs | 21 ++++++++---- .../Converters/Value/EnumConverter.cs | 4 +-- .../Converters/Value/GuidConverter.cs | 6 ++-- .../Converters/Value/Int32Converter.cs | 6 ++-- .../Converters/Value/ObjectConverter.cs | 6 ++-- .../Converters/Value/StringConverter.cs | 8 +++-- .../Text/Json/Serialization/JsonConverter.cs | 3 +- .../Serialization/JsonConverterFactory.cs | 10 ++++++ .../Json/Serialization/JsonConverterOfT.cs | 5 ++- .../Text/Json/Serialization/ReadStackFrame.cs | 9 +++--- 11 files changed, 73 insertions(+), 37 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 4dc43aa2a075e..f97686c98f7a0 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 @@ -95,12 +95,14 @@ internal sealed override bool OnTryRead( // Read method would have thrown if otherwise. Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); - ReadOnlySpan propertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options); + ReadOnlySpan unescapedPropertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options); + string unescapedPropertyNameAsString = JsonReaderHelper.TranscodeHelper(unescapedPropertyName); + // Copy key name to JsonPropertyName for JSON Path support in case of error. - state.Current.JsonPropertyName = propertyName.ToArray(); + state.Current.JsonPropertyNameAsString = unescapedPropertyNameAsString; JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); - TKey key = keyConverter.ReadWithQuotes(propertyName); + TKey key = keyConverter.ReadWithQuotes(unescapedPropertyName, unescapedPropertyNameAsString); // Read the value and add. reader.ReadWithVerify(); @@ -124,12 +126,14 @@ internal sealed override bool OnTryRead( // Read method would have thrown if otherwise. Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); - ReadOnlySpan propertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options); + ReadOnlySpan unescapedPropertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options); + string unescapedPropertyNameAsString = JsonReaderHelper.TranscodeHelper(unescapedPropertyName); + // Copy key name to JsonPropertyName for JSON Path support in case of error. - state.Current.JsonPropertyName = propertyName.ToArray(); + state.Current.JsonPropertyNameAsString = unescapedPropertyNameAsString; JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); - TKey key = keyConverter.ReadWithQuotes(propertyName); + TKey key = keyConverter.ReadWithQuotes(unescapedPropertyName, unescapedPropertyNameAsString); reader.ReadWithVerify(); @@ -220,10 +224,18 @@ internal sealed override bool OnTryRead( state.Current.PropertyState = StackFramePropertyState.Name; - ReadOnlySpan propertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options); - state.Current.DictionaryKeyName = propertyName.ToArray(); + ReadOnlySpan unescapedPropertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options); + // Copy key name to JsonPropertyName for JSON Path support in case of error. - state.Current.JsonPropertyName = state.Current.DictionaryKeyName; + if (typeof(TKey) == typeof(string)) + { + // We use this property for string keys in order to avoid the allocation of JsonPropertyName in the hot path. + state.Current.JsonPropertyNameAsString = JsonReaderHelper.TranscodeHelper(unescapedPropertyName); + } + else + { + state.Current.JsonPropertyName = unescapedPropertyName.ToArray(); + } } if (state.Current.PropertyState < StackFramePropertyState.ReadValue) @@ -240,7 +252,7 @@ internal sealed override bool OnTryRead( if (state.Current.PropertyState < StackFramePropertyState.TryRead) { JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); - TKey key = keyConverter.ReadWithQuotes(state.Current.DictionaryKeyName); + TKey key = keyConverter.ReadWithQuotes(state.Current.JsonPropertyName, state.Current.JsonPropertyNameAsString); // Get the value from the converter and add it. bool success = elementConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValue element); if (!success) 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 10f1d68a58718..47fcea13c40a2 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 @@ -67,12 +67,6 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio enumerator = (IDictionaryEnumerator)state.Current.CollectionEnumerator; } - // Avoid using GetKeyConverter here - // IDictionary is a spacial case since it has polymorphic object semantics on serialization. - // But needs to use JsonConverter on deserialization. - JsonClassInfo objectKeyClassInfo = options.GetOrAddClass(typeof(object)); - JsonConverter keyConverter = (JsonConverter)objectKeyClassInfo.PropertyInfoForClassInfo.ConverterBase; - JsonConverter converter = GetValueConverter(ref state); do { @@ -86,7 +80,20 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio { state.Current.PropertyState = StackFramePropertyState.Name; object key = enumerator.Key; - keyConverter.WriteWithQuotes(writer, key, options, ref state); + // Optimize for string since that's the hot path. + if (key is string keyString) + { + JsonConverter stringKeyConverter = GetKeyConverter(state.Current.JsonClassInfo); + stringKeyConverter.WriteWithQuotes(writer, keyString, options, ref state); + } + else + { + // IDictionary is a spacial case since it has polymorphic object semantics on serialization + // but needs to use JsonConverter on deserialization. + JsonClassInfo classInfo = options.GetOrAddClass(typeof(object)); + JsonConverter objectKeyConverter = (JsonConverter)classInfo.PropertyInfoForClassInfo.ConverterBase; + objectKeyConverter.WriteWithQuotes(writer, key, options, ref state); + } } object? element = enumerator.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 5485caab39339..3f33fdf868d88 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 @@ -318,9 +318,9 @@ private string FormatEnumValueToString(string value, JavaScriptEncoder? encoder) return converted; } - internal override T ReadWithQuotes(ReadOnlySpan utf8Bytes) + internal override T ReadWithQuotes(ReadOnlySpan unescapedPropertyName, string? unescapedPropertyNameAsString) { - string enumString = JsonReaderHelper.TranscodeHelper(utf8Bytes); + string enumString = JsonReaderHelper.TranscodeHelper(unescapedPropertyName); if (!Enum.TryParse(enumString, out T value) && !Enum.TryParse(enumString, ignoreCase: true, out value)) 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 eca00900f1705..ae53399b32b57 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 @@ -19,10 +19,10 @@ public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOpti writer.WriteStringValue(value); } - internal override Guid ReadWithQuotes(ReadOnlySpan span) + internal override Guid ReadWithQuotes(ReadOnlySpan unescapedPropertyName, string? unescapedPropertyNameAsString) { - if (Utf8Parser.TryParse(span, out Guid value, out int bytesConsumed) - && span.Length == bytesConsumed) + if (Utf8Parser.TryParse(unescapedPropertyName, out Guid value, out int bytesConsumed) + && unescapedPropertyName.Length == bytesConsumed) { return 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 66819a36b30b2..b7db71dd31e98 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 @@ -19,10 +19,10 @@ public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptio writer.WriteNumberValue(value); } - internal override int ReadWithQuotes(ReadOnlySpan span) + internal override int ReadWithQuotes(ReadOnlySpan unescapedPropertyName, string? unescapedPropertyNameAsString) { - if (Utf8Parser.TryParse(span, out int value, out int bytesConsumed) - && span.Length == bytesConsumed) + if (Utf8Parser.TryParse(unescapedPropertyName, out int value, out int bytesConsumed) + && unescapedPropertyName.Length == bytesConsumed) { return 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 099edb7607c5f..c2d9799111a03 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 @@ -21,7 +21,7 @@ public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOp throw new InvalidOperationException(); } - internal override object ReadWithQuotes(ReadOnlySpan utf8Bytes) + internal override object ReadWithQuotes(ReadOnlySpan unescapedPropertyName, string? unescapedPropertyNameAsString) => throw new NotSupportedException(); internal override void WriteWithQuotes(Utf8JsonWriter writer, object value, JsonSerializerOptions options, ref WriteStack state) @@ -34,8 +34,8 @@ private JsonConverter GetRuntimeConverter(Type runtimeType, Type parentType, Jso { JsonConverter runtimeConverter = options.GetOrAddClass(runtimeType).PropertyInfoForClassInfo.ConverterBase; - // We don't support object itself as TKey, only the other supported types when they are boxed. - if (runtimeConverter is JsonConverter || !runtimeConverter.CanBeDictionaryKey) + // We don't support object itself as key type. + if (runtimeConverter == this || !runtimeConverter.CanBeDictionaryKey) { ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(parentType); } 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 91ff9f7cdf247..031049982e926 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 @@ -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.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace System.Text.Json.Serialization.Converters @@ -18,8 +19,11 @@ public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerO writer.WriteStringValue(value); } - internal override string ReadWithQuotes(ReadOnlySpan utf8Bytes) - => JsonReaderHelper.TranscodeHelper(utf8Bytes); + internal override string ReadWithQuotes(ReadOnlySpan unescapedPropertyName, string? unescapedPropertyNameAsString) + { + Debug.Assert(unescapedPropertyNameAsString != null); + return unescapedPropertyNameAsString; + } internal override void WriteWithQuotes(Utf8JsonWriter writer, [DisallowNull] string? value, JsonSerializerOptions options, ref WriteStack state) { 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 a223653de5b53..ee4ef7bb9eee6 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 @@ -78,8 +78,7 @@ internal bool ShouldFlush(Utf8JsonWriter writer, ref WriteStack state) /// /// Loosely-typed WriteWithQuotes() that forwards to strongly-typed WriteWithQuotes(). /// - internal virtual void WriteWithQuotesAsObject(Utf8JsonWriter writer, object value, JsonSerializerOptions options, ref WriteStack state) - => throw new InvalidOperationException(); + internal abstract void WriteWithQuotesAsObject(Utf8JsonWriter writer, object value, JsonSerializerOptions options, ref WriteStack state); // Whether a type (ClassType.Object) is deserialized using a parameterized constructor. internal virtual bool ConstructorIsParameterized => false; 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 a9ca08b4a1157..55f717ee7a7c8 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 @@ -113,5 +113,15 @@ internal sealed override bool WriteCoreAsObject( throw new InvalidOperationException(); } + + internal sealed override void WriteWithQuotesAsObject( + Utf8JsonWriter writer, object value, + JsonSerializerOptions options, + ref WriteStack state) + { + Debug.Fail("We should never get here."); + + throw new InvalidOperationException(); + } } } 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 25f8a10a91ad3..bd78f8f65e90b 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 @@ -437,7 +437,10 @@ internal void VerifyWrite(int originalDepth, Utf8JsonWriter writer) /// The being used. public abstract void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options); - internal virtual T ReadWithQuotes(ReadOnlySpan utf8Bytes) + // When overriden in a derived class + // Always use unescapedPropertyNameAsString for typeof(T) == typeof(string). + // Always use unescapedPropertyName for typeof(T) != typeof(string). + internal virtual T ReadWithQuotes(ReadOnlySpan unescpaedPropertyName, string? unescapedPropertyNameAsString) => throw new InvalidOperationException(); internal virtual void WriteWithQuotes(Utf8JsonWriter writer, [DisallowNull] T value, JsonSerializerOptions options, ref WriteStack state) 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 e09174a332b5f..db3b10b01c710 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 @@ -15,10 +15,11 @@ internal struct ReadStackFrame public StackFramePropertyState PropertyState; public bool UseExtensionProperty; - // Support JSON Path on exceptions. - public byte[]? JsonPropertyName; // This is Utf8 since we don't want to convert to string until an exception is thown. - public byte[]? DictionaryKeyName; // This is Utf8 since we don't want to convert to TKey until we have both key and value when parsing the dictionary elements on stream cases. - public string? JsonPropertyNameAsString; // This is used for dictionary keys and re-entry cases that specify a property name. + // Support JSON Path on exceptions and non-string Dictionary keys. + // This is Utf8 since we don't want to convert to string until an exception is thown. + // For dictionary keys we don't want to convert to TKey until we have both key and value when parsing the dictionary elements on stream cases. + public byte[]? JsonPropertyName; + public string? JsonPropertyNameAsString; // This is used for string dictionary keys and re-entry cases that specify a property name. // Validation state. public int OriginalDepth; From 6bcb25eb71d452c347a81cd4b5ca36923483e8fe Mon Sep 17 00:00:00 2001 From: David Cantu Date: Mon, 22 Jun 2020 16:31:06 -0700 Subject: [PATCH 04/16] Add Read/WriteWithQuotes to the remaining supported types --- .../src/Resources/Strings.resx | 3 + .../src/System/Text/Json/JsonConstants.cs | 1 + .../Text/Json/Reader/Utf8JsonReader.TryGet.cs | 61 +++++++ .../Collection/DictionaryDefaultConverter.cs | 88 +++++++--- .../DictionaryOfTKeyTValueConverter.cs | 3 +- .../Collection/IDictionaryConverter.cs | 2 +- .../IDictionaryOfTKeyTValueConverter.cs | 2 +- ...ReadOnlyDictionaryOfTKeyTValueConverter.cs | 2 +- ...mmutableDictionaryOfTKeyTValueConverter.cs | 2 +- .../Converters/Value/BooleanConverter.cs | 21 +++ .../Converters/Value/ByteConverter.cs | 17 ++ .../Converters/Value/CharConverter.cs | 23 +++ .../Converters/Value/DateTimeConverter.cs | 17 ++ .../Value/DateTimeOffsetConverter.cs | 17 ++ .../Converters/Value/DecimalConverter.cs | 17 ++ .../Converters/Value/DoubleConverter.cs | 17 ++ .../Converters/Value/EnumConverter.cs | 6 +- .../Converters/Value/GuidConverter.cs | 17 +- .../Converters/Value/Int16Converter.cs | 19 ++ .../Converters/Value/Int32Converter.cs | 17 +- .../Converters/Value/Int64Converter.cs | 17 ++ .../Converters/Value/ObjectConverter.cs | 5 +- .../Converters/Value/SByteConverter.cs | 19 ++ .../Converters/Value/SingleConverter.cs | 19 ++ .../Converters/Value/StringConverter.cs | 6 +- .../Text/Json/Serialization/JsonClassInfo.cs | 9 +- .../Json/Serialization/JsonConverterOfT.cs | 5 +- .../Text/Json/Serialization/ReadStackFrame.cs | 3 + .../src/System/Text/Json/ThrowHelper.cs | 4 + ...Utf8JsonWriter.WriteProperties.DateTime.cs | 7 + ...onWriter.WriteProperties.DateTimeOffset.cs | 7 + .../Utf8JsonWriter.WriteProperties.Decimal.cs | 8 + .../Utf8JsonWriter.WriteProperties.Double.cs | 9 + .../Utf8JsonWriter.WriteProperties.Float.cs | 8 + .../Utf8JsonWriter.WriteProperties.Guid.cs | 8 + .../Utf8JsonWriter.WriteProperties.Literal.cs | 11 ++ ...JsonWriter.WriteProperties.SignedNumber.cs | 13 ++ ...CollectionTests.Dictionary.NonStringKey.cs | 164 +++++++++++++++--- .../CollectionTests.Dictionary.cs | 4 + 39 files changed, 581 insertions(+), 97 deletions(-) diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index 1a029bf64b235..de04df8cab1ed 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -527,4 +527,7 @@ The value cannot be 'JsonIgnoreCondition.Always'. + + The JSON value is not in a supported Boolean format. + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs index 1d13e73cdda8d..0e695e0435372 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs @@ -66,6 +66,7 @@ internal static class JsonConstants public const int MaxBase64ValueTokenSize = (MaxEscapedTokenSize >> 2) * 3 / MaxExpansionFactorWhileEscaping; // 125_000_000 bytes public const int MaxCharacterTokenSize = MaxEscapedTokenSize / MaxExpansionFactorWhileEscaping; // 166_666_666 characters + public const int MaximumFormatBooleanLength = 5; public const int MaximumFormatInt64Length = 20; // 19 + sign (i.e. -9223372036854775808) public const int MaximumFormatUInt64Length = 20; // i.e. 18446744073709551615 public const int MaximumFormatDoubleLength = 128; // default (i.e. 'G'), using 128 (rather than say 32) to be future-proof. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs index 642d4a6db5f1b..5dc1afbf56053 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs @@ -465,6 +465,11 @@ public bool TryGetBytesFromBase64([NotNullWhen(true)] out byte[]? value) throw ThrowHelper.GetInvalidOperationException_ExpectedString(TokenType); } + return TryGetBytesFromBase64Core(out value); + } + + internal bool TryGetBytesFromBase64Core([NotNullWhen(true)] out byte[]? value) + { ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (_stringHasEscaping) @@ -495,6 +500,11 @@ public bool TryGetByte(out byte value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } + return TryGetByteCore(out value); + } + + internal bool TryGetByteCore(out byte value) + { ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out byte tmp, out int bytesConsumed) && span.Length == bytesConsumed) @@ -525,6 +535,11 @@ public bool TryGetSByte(out sbyte value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } + return TryGetSByteCore(out value); + } + + internal bool TryGetSByteCore(out sbyte value) + { ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out sbyte tmp, out int bytesConsumed) && span.Length == bytesConsumed) @@ -554,6 +569,11 @@ public bool TryGetInt16(out short value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } + return TryGetInt16Core(out value); + } + + internal bool TryGetInt16Core(out short value) + { ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out short tmp, out int bytesConsumed) && span.Length == bytesConsumed) @@ -583,6 +603,11 @@ public bool TryGetInt32(out int value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } + return TryGetInt32Core(out value); + } + + internal bool TryGetInt32Core(out int value) + { ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out int tmp, out int bytesConsumed) && span.Length == bytesConsumed) @@ -612,6 +637,11 @@ public bool TryGetInt64(out long value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } + return TryGetInt64Core(out value); + } + + internal bool TryGetInt64Core(out long value) + { ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out long tmp, out int bytesConsumed) && span.Length == bytesConsumed) @@ -731,6 +761,11 @@ public bool TryGetSingle(out float value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } + return TryGetSingleCore(out value); + } + + internal bool TryGetSingleCore(out float value) + { ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out float tmp, out int bytesConsumed, _numberFormat) && span.Length == bytesConsumed) @@ -760,6 +795,11 @@ public bool TryGetDouble(out double value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } + return TryGetDoubleCore(out value); + } + + internal bool TryGetDoubleCore(out double value) + { ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out double tmp, out int bytesConsumed, _numberFormat) && span.Length == bytesConsumed) @@ -789,6 +829,11 @@ public bool TryGetDecimal(out decimal value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } + return TryGetDecimalCore(out value); + } + + internal bool TryGetDecimalCore(out decimal value) + { ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out decimal tmp, out int bytesConsumed, _numberFormat) && span.Length == bytesConsumed) @@ -818,6 +863,12 @@ public bool TryGetDateTime(out DateTime value) throw ThrowHelper.GetInvalidOperationException_ExpectedString(TokenType); } + return TryGetDateTimeCore(out value); + } + + internal bool TryGetDateTimeCore(out DateTime value) + { + ReadOnlySpan span = stackalloc byte[0]; if (HasValueSequence) @@ -881,6 +932,11 @@ public bool TryGetDateTimeOffset(out DateTimeOffset value) throw ThrowHelper.GetInvalidOperationException_ExpectedString(TokenType); } + return TryGetDateTimeOffsetCore(out value); + } + + internal bool TryGetDateTimeOffsetCore(out DateTimeOffset value) + { ReadOnlySpan span = stackalloc byte[0]; if (HasValueSequence) @@ -945,6 +1001,11 @@ public bool TryGetGuid(out Guid value) throw ThrowHelper.GetInvalidOperationException_ExpectedString(TokenType); } + return TryGetGuidCore(out value); + } + + internal bool TryGetGuidCore(out Guid value) + { ReadOnlySpan span = stackalloc byte[0]; if (HasValueSequence) 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 8bbbb0122551f..d027f83717d8e 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 @@ -17,7 +17,7 @@ internal abstract class DictionaryDefaultConverter /// /// When overridden, adds the value to the collection. /// - protected abstract void Add(in TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state); + protected abstract void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state); /// /// When overridden, converts the temporary collection held in state.Current.ReturnValue to the final collection. @@ -32,6 +32,7 @@ protected virtual void CreateCollection(ref Utf8JsonReader reader, ref ReadStack internal override Type ElementType => typeof(TValue); internal override Type KeyType => typeof(TKey); + protected bool IsStringKey = typeof(TKey) == typeof(string); protected static JsonConverter GetElementConverter(ref ReadStack state) { @@ -95,15 +96,31 @@ internal sealed override bool OnTryRead( // Read method would have thrown if otherwise. Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); - ReadOnlySpan unescapedPropertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options); - string unescapedPropertyNameAsString = JsonReaderHelper.TranscodeHelper(unescapedPropertyName); - - // Copy key name to JsonPropertyName for JSON Path support in case of error. - state.Current.JsonPropertyNameAsString = unescapedPropertyNameAsString; + if (options.ReferenceHandler != null) + { + ReadOnlySpan propertyName = reader.GetSpan(); + if (propertyName.Length > 0 && propertyName[0] == '$') + { + ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state); + } + } JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); - TKey key = keyConverter.ReadWithQuotes(unescapedPropertyName, unescapedPropertyNameAsString); + TKey key = keyConverter.ReadWithQuotes(ref reader); + // Copy key name for JSON Path support in case of error. + string unescapedPropertyNameAsString; + if (IsStringKey) + { + // Special case to avoid calling again GetString() + unescapedPropertyNameAsString = (string)(object)key; + } + else + { + unescapedPropertyNameAsString = reader.GetString()!; + } + + state.Current.JsonPropertyNameAsString = unescapedPropertyNameAsString; // Read the value and add. reader.ReadWithVerify(); TValue element = elementConverter.Read(ref reader, typeof(TValue), options); @@ -126,15 +143,31 @@ internal sealed override bool OnTryRead( // Read method would have thrown if otherwise. Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); - ReadOnlySpan unescapedPropertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options); - string unescapedPropertyNameAsString = JsonReaderHelper.TranscodeHelper(unescapedPropertyName); - - // Copy key name to JsonPropertyName for JSON Path support in case of error. - state.Current.JsonPropertyNameAsString = unescapedPropertyNameAsString; + if (options.ReferenceHandler != null) + { + ReadOnlySpan propertyName = reader.GetSpan(); + if (propertyName.Length > 0 && propertyName[0] == '$') + { + ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state); + } + } JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); - TKey key = keyConverter.ReadWithQuotes(unescapedPropertyName, unescapedPropertyNameAsString); + TKey key = keyConverter.ReadWithQuotes(ref reader); + // Copy key name for JSON Path support in case of error. + string unescapedPropertyNameAsString; + if (IsStringKey) + { + // Special case to avoid calling again GetString() + unescapedPropertyNameAsString = (string)(object)key; + } + else + { + unescapedPropertyNameAsString = reader.GetString()!; + } + + state.Current.JsonPropertyNameAsString = unescapedPropertyNameAsString; reader.ReadWithVerify(); // Get the value from the converter and add it. @@ -224,18 +257,32 @@ internal sealed override bool OnTryRead( state.Current.PropertyState = StackFramePropertyState.Name; - ReadOnlySpan unescapedPropertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options); + if (options.ReferenceHandler != null) + { + ReadOnlySpan propertyName = reader.GetSpan(); + if (propertyName.Length > 0 && propertyName[0] == '$') + { + ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state); + } + } + + JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); + TKey key = keyConverter.ReadWithQuotes(ref reader); - // Copy key name to JsonPropertyName for JSON Path support in case of error. - if (typeof(TKey) == typeof(string)) + // Copy key name for JSON Path support in case of error. + string unescapedPropertyNameAsString; + if (IsStringKey) { - // We use this property for string keys in order to avoid the allocation of JsonPropertyName in the hot path. - state.Current.JsonPropertyNameAsString = JsonReaderHelper.TranscodeHelper(unescapedPropertyName); + // Special case to avoid calling again GetString() + unescapedPropertyNameAsString = (string)(object)key; } else { - state.Current.JsonPropertyName = unescapedPropertyName.ToArray(); + unescapedPropertyNameAsString = reader.GetString()!; } + + state.Current.JsonPropertyNameAsString = unescapedPropertyNameAsString; + state.Current.DictionaryKey = key; } if (state.Current.PropertyState < StackFramePropertyState.ReadValue) @@ -251,8 +298,6 @@ internal sealed override bool OnTryRead( if (state.Current.PropertyState < StackFramePropertyState.TryRead) { - JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); - TKey key = keyConverter.ReadWithQuotes(state.Current.JsonPropertyName, state.Current.JsonPropertyNameAsString); // Get the value from the converter and add it. bool success = elementConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValue element); if (!success) @@ -261,6 +306,7 @@ internal sealed override bool OnTryRead( return false; } + TKey key = (TKey)state.Current.DictionaryKey!; Add(key, element!, options, ref state); state.Current.EndElement(); } 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 67e7a35716621..e2f27b4d3c53c 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 @@ -15,9 +15,8 @@ internal sealed class DictionaryOfTKeyTValueConverter where TCollection : Dictionary where TKey : notnull { - protected override void Add(in TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) { - //string key = state.Current.JsonPropertyNameAsString!; ((TCollection)state.Current.ReturnValue!)[key] = 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 83b031efbdc50..dc30d198628c9 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 @@ -15,7 +15,7 @@ internal sealed class IDictionaryConverter : DictionaryDefaultConverter where TCollection : IDictionary { - protected override void Add(in string key, in object? value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(string key, in object? value, JsonSerializerOptions options, ref ReadStack state) { ((IDictionary)state.Current.ReturnValue!)[key] = value; } 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 6bf7e3327e3d4..5e2abe152c7bb 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 @@ -15,7 +15,7 @@ internal sealed class IDictionaryOfTKeyTValueConverter where TKey : notnull { - protected override void Add(in TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) { ((TCollection)state.Current.ReturnValue!)[key] = value; } 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 d322bfb4c2827..8ed53da05d301 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 @@ -11,7 +11,7 @@ internal sealed class IReadOnlyDictionaryOfTKeyTValueConverter where TKey : notnull { - protected override void Add(in TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) { ((Dictionary)state.Current.ReturnValue!)[key] = value; } 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 8c0d7491754bc..dc7fc583f9738 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 @@ -11,7 +11,7 @@ internal sealed class ImmutableDictionaryOfTKeyTValueConverter where TKey : notnull { - protected override void Add(in TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) { ((Dictionary)state.Current.ReturnValue!)[key] = value; } 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 04da6ed163fc5..0f76cee033d3e 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 @@ -2,6 +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.Text; + namespace System.Text.Json.Serialization.Converters { internal sealed class BooleanConverter : JsonConverter @@ -15,5 +17,24 @@ public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOpti { writer.WriteBooleanValue(value); } + + internal override bool ReadWithQuotes(ref Utf8JsonReader reader) + { + ReadOnlySpan propertyName = reader.GetSpan(); + if (Utf8Parser.TryParse(propertyName, out bool value, out int bytesConsumed) + && propertyName.Length == bytesConsumed) + { + return value; + } + + throw ThrowHelper.GetFormatException(DataType.Boolean); + } + + internal override void WriteWithQuotes(Utf8JsonWriter writer, bool value, JsonSerializerOptions options, ref WriteStack state) + { + writer.WritePropertyName(value); + } + + internal override bool CanBeDictionaryKey => true; } } 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 38b8211ee3619..e40a2518a9f78 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 @@ -15,5 +15,22 @@ public override void Write(Utf8JsonWriter writer, byte value, JsonSerializerOpti { writer.WriteNumberValue(value); } + + internal override byte ReadWithQuotes(ref Utf8JsonReader reader) + { + if (!reader.TryGetByteCore(out byte value)) + { + throw ThrowHelper.GetFormatException(NumericType.Byte); + } + + return value; + } + + internal override void WriteWithQuotes(Utf8JsonWriter writer, byte value, JsonSerializerOptions options, ref WriteStack state) + { + writer.WritePropertyName(value); + } + + internal override bool CanBeDictionaryKey => true; } } 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 5b259892c4d9c..07968219b4403 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,5 +28,28 @@ public override void Write(Utf8JsonWriter writer, char value, JsonSerializerOpti #endif ); } + + internal override char ReadWithQuotes(ref Utf8JsonReader reader) + { + string? str = reader.GetString(); + if (string.IsNullOrEmpty(str) || str.Length > 1) + { + throw ThrowHelper.GetInvalidOperationException_ExpectedChar(JsonTokenType.String); + } + return str[0]; + } + + internal override void WriteWithQuotes(Utf8JsonWriter writer, char value, JsonSerializerOptions options, ref WriteStack state) + { + writer.WritePropertyName( +#if BUILDING_INBOX_LIBRARY + MemoryMarshal.CreateSpan(ref value, 1) +#else + value.ToString() +#endif + ); + } + + internal override bool CanBeDictionaryKey => true; } } 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 e5dbbd588271f..a55a6cb213c25 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,5 +15,22 @@ public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializer { writer.WriteStringValue(value); } + + internal override DateTime ReadWithQuotes(ref Utf8JsonReader reader) + { + if (!reader.TryGetDateTimeCore(out DateTime value)) + { + throw ThrowHelper.GetFormatException(DataType.DateTime); + } + + return value; + } + + internal override void WriteWithQuotes(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options, ref WriteStack state) + { + writer.WritePropertyName(value); + } + + internal override bool CanBeDictionaryKey => true; } } 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 927911cb7bd54..02c0a946a736b 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,5 +15,22 @@ public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSeri { writer.WriteStringValue(value); } + + internal override DateTimeOffset ReadWithQuotes(ref Utf8JsonReader reader) + { + if (!reader.TryGetDateTimeOffsetCore(out DateTimeOffset value)) + { + throw ThrowHelper.GetFormatException(DataType.DateTimeOffset); + } + + return value; + } + + internal override void WriteWithQuotes(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options, ref WriteStack state) + { + writer.WritePropertyName(value); + } + + internal override bool CanBeDictionaryKey => true; } } 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 326df60822c75..14d12c85a122c 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 @@ -15,5 +15,22 @@ public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerO { writer.WriteNumberValue(value); } + + internal override decimal ReadWithQuotes(ref Utf8JsonReader reader) + { + if (!reader.TryGetDecimalCore(out decimal value)) + { + throw ThrowHelper.GetFormatException(NumericType.Decimal); + } + + return value; + } + + internal override void WriteWithQuotes(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options, ref WriteStack state) + { + writer.WritePropertyName(value); + } + + internal override bool CanBeDictionaryKey => true; } } 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 4a2acd60865c9..67ff41c7852ed 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 @@ -15,5 +15,22 @@ public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOp { writer.WriteNumberValue(value); } + + internal override double ReadWithQuotes(ref Utf8JsonReader reader) + { + if (!reader.TryGetDoubleCore(out double value)) + { + throw ThrowHelper.GetFormatException(NumericType.Double); + } + + return value; + } + + internal override void WriteWithQuotes(Utf8JsonWriter writer, double value, JsonSerializerOptions options, ref WriteStack state) + { + writer.WritePropertyName(value); + } + + internal override bool CanBeDictionaryKey => true; } } 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 3f33fdf868d88..fbc7dc52db1a1 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 @@ -318,15 +318,15 @@ private string FormatEnumValueToString(string value, JavaScriptEncoder? encoder) return converted; } - internal override T ReadWithQuotes(ReadOnlySpan unescapedPropertyName, string? unescapedPropertyNameAsString) + internal override T ReadWithQuotes(ref Utf8JsonReader reader) { - string enumString = JsonReaderHelper.TranscodeHelper(unescapedPropertyName); + string? enumString = reader.GetString(); + Debug.Assert(enumString != null); if (!Enum.TryParse(enumString, out T value) && !Enum.TryParse(enumString, ignoreCase: true, out value)) { ThrowHelper.ThrowJsonException(); - return default; } return value; 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 ae53399b32b57..7b6ae9ca88d9b 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 @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Buffers.Text; -using System.Diagnostics; namespace System.Text.Json.Serialization.Converters { @@ -19,25 +18,19 @@ public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOpti writer.WriteStringValue(value); } - internal override Guid ReadWithQuotes(ReadOnlySpan unescapedPropertyName, string? unescapedPropertyNameAsString) + internal override Guid ReadWithQuotes(ref Utf8JsonReader reader) { - if (Utf8Parser.TryParse(unescapedPropertyName, out Guid value, out int bytesConsumed) - && unescapedPropertyName.Length == bytesConsumed) + if (!reader.TryGetGuidCore(out Guid value)) { - return value; + throw ThrowHelper.GetFormatException(DataType.Guid); } - throw ThrowHelper.GetFormatException(DataType.Guid); + return value; } internal override void WriteWithQuotes(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options, ref WriteStack state) { - Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatGuidLength]; - - bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten); - Debug.Assert(result); - - writer.WritePropertyName(utf8PropertyName); + writer.WritePropertyName(value); } internal override bool CanBeDictionaryKey => true; 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 f68e23459af7e..ea5bc612b962c 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 @@ -2,6 +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.Diagnostics.CodeAnalysis; + namespace System.Text.Json.Serialization.Converters { internal sealed class Int16Converter : JsonConverter @@ -15,5 +17,22 @@ public override void Write(Utf8JsonWriter writer, short value, JsonSerializerOpt { writer.WriteNumberValue(value); } + + internal override short ReadWithQuotes(ref Utf8JsonReader reader) + { + if (!reader.TryGetInt16Core(out short value)) + { + throw ThrowHelper.GetFormatException(NumericType.Int16); + } + + return value; + } + + internal override void WriteWithQuotes(Utf8JsonWriter writer, [DisallowNull] short value, JsonSerializerOptions options, ref WriteStack state) + { + writer.WritePropertyName(value); + } + + internal override bool CanBeDictionaryKey => true; } } 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 b7db71dd31e98..db1ed78a6a989 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 @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Buffers.Text; -using System.Diagnostics; namespace System.Text.Json.Serialization.Converters { @@ -19,25 +18,19 @@ public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptio writer.WriteNumberValue(value); } - internal override int ReadWithQuotes(ReadOnlySpan unescapedPropertyName, string? unescapedPropertyNameAsString) + internal override int ReadWithQuotes(ref Utf8JsonReader reader) { - if (Utf8Parser.TryParse(unescapedPropertyName, out int value, out int bytesConsumed) - && unescapedPropertyName.Length == bytesConsumed) + if (!reader.TryGetInt32Core(out int value)) { - return value; + throw ThrowHelper.GetFormatException(NumericType.Int32); } - throw ThrowHelper.GetFormatException(NumericType.Int32); + return value; } internal override void WriteWithQuotes(Utf8JsonWriter writer, int value, JsonSerializerOptions options, ref WriteStack state) { - Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatInt64Length]; - - bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten); - Debug.Assert(result); - - writer.WritePropertyName(utf8PropertyName.Slice(0, bytesWritten)); + writer.WritePropertyName(value); } internal override bool CanBeDictionaryKey => true; 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 0ea30c22a5644..623099520552a 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 @@ -15,5 +15,22 @@ public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOpti { writer.WriteNumberValue(value); } + + internal override long ReadWithQuotes(ref Utf8JsonReader reader) + { + if (!reader.TryGetInt64Core(out long value)) + { + throw ThrowHelper.GetFormatException(NumericType.Int64); + } + + return value; + } + + internal override void WriteWithQuotes(Utf8JsonWriter writer, long value, JsonSerializerOptions options, ref WriteStack state) + { + writer.WritePropertyName(value); + } + + internal override bool CanBeDictionaryKey => true; } } 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 c2d9799111a03..cd4dbbe7cf51e 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 @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Diagnostics.CodeAnalysis; - namespace System.Text.Json.Serialization.Converters { internal sealed class ObjectConverter : JsonConverter @@ -21,7 +19,7 @@ public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOp throw new InvalidOperationException(); } - internal override object ReadWithQuotes(ReadOnlySpan unescapedPropertyName, string? unescapedPropertyNameAsString) + internal override object ReadWithQuotes(ref Utf8JsonReader reader) => throw new NotSupportedException(); internal override void WriteWithQuotes(Utf8JsonWriter writer, object value, JsonSerializerOptions options, ref WriteStack state) @@ -34,7 +32,6 @@ private JsonConverter GetRuntimeConverter(Type runtimeType, Type parentType, Jso { JsonConverter runtimeConverter = options.GetOrAddClass(runtimeType).PropertyInfoForClassInfo.ConverterBase; - // We don't support object itself as key type. if (runtimeConverter == this || !runtimeConverter.CanBeDictionaryKey) { ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(parentType); 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 bbc004a29c07a..36b9dd157a1f1 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 @@ -2,6 +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.Diagnostics.CodeAnalysis; + namespace System.Text.Json.Serialization.Converters { internal sealed class SByteConverter : JsonConverter @@ -15,5 +17,22 @@ public override void Write(Utf8JsonWriter writer, sbyte value, JsonSerializerOpt { writer.WriteNumberValue(value); } + + internal override sbyte ReadWithQuotes(ref Utf8JsonReader reader) + { + if (!reader.TryGetSByteCore(out sbyte value)) + { + throw ThrowHelper.GetFormatException(NumericType.SByte); + } + + return value; + } + + internal override void WriteWithQuotes(Utf8JsonWriter writer, [DisallowNull] sbyte value, JsonSerializerOptions options, ref WriteStack state) + { + writer.WritePropertyName(value); + } + + internal override bool CanBeDictionaryKey => true; } } 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 c7c8b180ff5a8..14e82af8fae9f 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 @@ -2,6 +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.Diagnostics.CodeAnalysis; + namespace System.Text.Json.Serialization.Converters { internal sealed class SingleConverter : JsonConverter @@ -15,5 +17,22 @@ public override void Write(Utf8JsonWriter writer, float value, JsonSerializerOpt { writer.WriteNumberValue(value); } + + internal override float ReadWithQuotes(ref Utf8JsonReader reader) + { + if (!reader.TryGetSingleCore(out float value)) + { + throw ThrowHelper.GetFormatException(NumericType.Single); + } + + return value; + } + + internal override void WriteWithQuotes(Utf8JsonWriter writer, [DisallowNull] float value, JsonSerializerOptions options, ref WriteStack state) + { + writer.WritePropertyName(value); + } + + internal override bool CanBeDictionaryKey => true; } } 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 031049982e926..110855d27f47c 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 @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace System.Text.Json.Serialization.Converters @@ -19,10 +18,9 @@ public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerO writer.WriteStringValue(value); } - internal override string ReadWithQuotes(ReadOnlySpan unescapedPropertyName, string? unescapedPropertyNameAsString) + internal override string ReadWithQuotes(ref Utf8JsonReader reader) { - Debug.Assert(unescapedPropertyNameAsString != null); - return unescapedPropertyNameAsString; + return reader.GetString()!; } internal override void WriteWithQuotes(Utf8JsonWriter writer, [DisallowNull] string? value, JsonSerializerOptions options, ref WriteStack state) 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 e17180925bbeb..f383a75769668 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 @@ -29,7 +29,7 @@ internal sealed partial class JsonClassInfo // If enumerable, the JsonClassInfo for the element type. private JsonClassInfo? _elementClassInfo; - // If dictionary, the JsonClassInfo for TKey. + // If dictionary, the JsonClassInfo for the key type. private JsonClassInfo? _keyClassInfo; /// @@ -62,7 +62,7 @@ public JsonClassInfo? KeyClassInfo if (_keyClassInfo == null && KeyType != null) { Debug.Assert(ClassType == ClassType.Dictionary); - + // TODO: When looking for the key converter, only consider types with an internal converter. _keyClassInfo = Options.GetOrAddClass(KeyType); } @@ -223,13 +223,10 @@ public JsonClassInfo(Type type, JsonSerializerOptions options) PropertyCache = cache; } break; + case ClassType.Enumerable: case ClassType.Dictionary: { KeyType = converter.KeyType; - goto case ClassType.Enumerable; - } - case ClassType.Enumerable: - { ElementType = converter.ElementType; CreateObject = options.MemberAccessorStrategy.CreateConstructor(runtimeType); } 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 020e2b4436d48..a3f1f432654b3 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 @@ -437,10 +437,7 @@ internal void VerifyWrite(int originalDepth, Utf8JsonWriter writer) /// The being used. public abstract void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options); - // When overriden in a derived class - // Always use unescapedPropertyNameAsString for typeof(T) == typeof(string). - // Always use unescapedPropertyName for typeof(T) != typeof(string). - internal virtual T ReadWithQuotes(ReadOnlySpan unescpaedPropertyName, string? unescapedPropertyNameAsString) + internal virtual T ReadWithQuotes(ref Utf8JsonReader reader) => throw new InvalidOperationException(); internal virtual void WriteWithQuotes(Utf8JsonWriter writer, [DisallowNull] T value, JsonSerializerOptions options, ref WriteStack state) 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 db3b10b01c710..74ff1bca6710d 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 @@ -21,6 +21,9 @@ internal struct ReadStackFrame public byte[]? JsonPropertyName; public string? JsonPropertyNameAsString; // This is used for string dictionary keys and re-entry cases that specify a property name. + // Stores the non-string dictionary keys for continuation. + public object? DictionaryKey; + // Validation state. public int OriginalDepth; public JsonTokenType OriginalTokenType; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs index 9dffc288b1a55..16b2471587091 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs @@ -612,6 +612,9 @@ public static FormatException GetFormatException(DataType dateType) switch (dateType) { + case DataType.Boolean: + message = SR.FormatBoolean; + break; case DataType.DateTime: message = SR.FormatDateTime; break; @@ -702,6 +705,7 @@ internal enum NumericType internal enum DataType { + Boolean, DateTime, DateTimeOffset, Base64String, diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs index da5219b98771d..3f5aaf6e60e5d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs @@ -375,5 +375,12 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTim output[BytesPending++] = JsonConstants.Quote; } + + internal void WritePropertyName(DateTime value) + { + Span buffer = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + JsonWriterHelper.WriteDateTimeTrimmed(buffer, value, out int bytesWritten); + WritePropertyName(buffer.Slice(0, bytesWritten)); + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs index 37cd479a2b735..b4d8549459dfa 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs @@ -374,5 +374,12 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTim output[BytesPending++] = JsonConstants.Quote; } + + internal void WritePropertyName(DateTimeOffset value) + { + Span buffer = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + JsonWriterHelper.WriteDateTimeOffsetTrimmed(buffer, value, out int bytesWritten); + WritePropertyName(buffer.Slice(0, bytesWritten)); + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs index e4b05e79b30fd..7d0facd51e782 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs @@ -362,5 +362,13 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, decimal Debug.Assert(result); BytesPending += bytesWritten; } + + internal void WritePropertyName(decimal value) + { + Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatDecimalLength]; + bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten); + Debug.Assert(result); + WritePropertyName(utf8PropertyName.Slice(0, bytesWritten)); + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs index f8ae05cc04c20..eab8b38d512d9 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs @@ -366,5 +366,14 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, double Debug.Assert(result); BytesPending += bytesWritten; } + + internal void WritePropertyName(double value) + { + JsonWriterHelper.ValidateDouble(value); + Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatDoubleLength]; + bool result = TryFormatDouble(value, utf8PropertyName, out int bytesWritten); + Debug.Assert(result); + WritePropertyName(utf8PropertyName.Slice(0, bytesWritten)); + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs index 60ba0b03872d0..27a1a2f93669b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs @@ -366,5 +366,13 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, float v Debug.Assert(result); BytesPending += bytesWritten; } + + internal void WritePropertyName(float value) + { + Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatSingleLength]; + bool result = TryFormatSingle(value, utf8PropertyName, out int bytesWritten); + Debug.Assert(result); + WritePropertyName(utf8PropertyName.Slice(0, bytesWritten)); + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs index b18a107c78b35..7983dd31d47ad 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs @@ -378,5 +378,13 @@ private void WriteStringIndented(ReadOnlySpan escapedPropertyName, Guid va output[BytesPending++] = JsonConstants.Quote; } + + internal void WritePropertyName(Guid value) + { + Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatGuidLength]; + bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten); + Debug.Assert(result); + WritePropertyName(utf8PropertyName.Slice(0, bytesWritten)); + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs index c5772f978005d..87b3dde623f8e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Buffers.Text; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -503,5 +504,15 @@ private void WriteLiteralIndented(ReadOnlySpan escapedPropertyName, ReadOn value.CopyTo(output.Slice(BytesPending)); BytesPending += value.Length; } + + internal void WritePropertyName(bool value) + { + Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatBooleanLength]; + + bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten); + Debug.Assert(result); + + WritePropertyName(utf8PropertyName.Slice(0, bytesWritten)); + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs index 360bea1089d0c..8768b9ce48f8e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs @@ -432,5 +432,18 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, long va Debug.Assert(result); BytesPending += bytesWritten; } + + internal void WritePropertyName(int value) + => WritePropertyName((long)value); + + internal void WritePropertyName(long value) + { + Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatInt64Length]; + + bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten); + Debug.Assert(result); + + WritePropertyName(utf8PropertyName.Slice(0, bytesWritten)); + } } } 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 3f43980a55c44..c9cd3dc61ac39 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 @@ -11,7 +11,7 @@ namespace System.Text.Json.Serialization.Tests { - public class DictionaryKeyTests + public partial class DictionaryTests { public abstract class DictionaryKeyTestsBase { @@ -63,16 +63,57 @@ public async Task TestDictionaryKeyAsync() } } - public class DictionaryIntKey : DictionaryKeyTestsBase + public class DictionaryBoolKey : DictionaryKeyTestsBase { - protected override int Key => 1; + protected override bool Key => true; protected override int Value => 1; } - public class DictionaryGuidKey : DictionaryKeyTestsBase + public class DictionaryByteKey : DictionaryKeyTestsBase { - // Use singleton pattern here so the Guid key does not change everytime this is called. - protected override Guid Key { get; } = Guid.NewGuid(); + protected override byte Key => byte.MaxValue; + protected override int Value => 1; + } + + public class DictionaryCharKey : DictionaryKeyTestsBase + { + protected override string _expectedJson => @"{""\uFFFF"":""\uFFFF""}"; + protected override char Key => char.MaxValue; + protected override char Value => char.MaxValue; + } + + public class DictionaryDateTimeKey : DictionaryKeyTestsBase + { + protected override string _expectedJson => $@"{{""{DateTime.MaxValue.ToString("O")}"":1}}"; + protected override DateTime Key => DateTime.MaxValue; + protected override int Value => 1; + } + + public class DictionaryDateTimeOffsetKey : DictionaryKeyTestsBase + { + //TODO: The plus sign is escaped for the key but not for the value. Is this correct? + protected override string _expectedJson => $@"{{""9999-12-31T23:59:59.9999999\u002B00:00"":""{DateTimeOffset.MaxValue.ToString("O")}""}}"; + protected override DateTimeOffset Key => DateTimeOffset.MaxValue; + protected override DateTimeOffset Value => DateTimeOffset.MaxValue; + } + + public class DictionaryDecimalKey : DictionaryKeyTestsBase + { + protected override decimal Key => decimal.MaxValue; + protected override int Value => 1; + } + + //public class DictionaryDoubleKey : DictionaryKeyTestsBase + //{ + // //TODO: The plus sign is escaped for the key but not for the value. Is this correct? + // protected override string _expectedJson => $@"{{""1.7976931348623157E\u002B308"":{double.MaxValue}}}"; + // protected override double Key => double.MaxValue; + // protected override double Value => double.MaxValue; + //} + + public class DictionaryDoubleKey : DictionaryKeyTestsBase + { + protected override double Key => 1; protected override int Value => 1; } @@ -88,6 +129,50 @@ public class DictionaryEnumFlagsKey : DictionaryKeyTestsBase protected override int Value => 1; } + public class DictionaryGuidKey : DictionaryKeyTestsBase + { + // Use singleton pattern here so the Guid key does not change everytime this is called. + protected override Guid Key { get; } = Guid.NewGuid(); + protected override int Value => 1; + } + + public class DictionaryInt16Key : DictionaryKeyTestsBase + { + protected override short Key => short.MaxValue; + protected override int Value => 1; + } + + public class DictionaryInt32Key : DictionaryKeyTestsBase + { + protected override int Key => int.MaxValue; + protected override int Value => 1; + } + + public class DictionaryInt64Key : DictionaryKeyTestsBase + { + protected override long Key => long.MaxValue; + protected override int Value => 1; + } + + public class DictionarySByteKey : DictionaryKeyTestsBase + { + protected override sbyte Key => sbyte.MaxValue; + protected override int Value => 1; + } + + //public class DictionarySingleKey : DictionaryKeyTestsBase + //{ + // //TODO: The plus sign is escaped for the key but not for the value. Is this correct? + // protected override float Key => float.MaxValue; + // protected override float Value => float.MaxValue; + //} + + public class DictionarySingleKey : DictionaryKeyTestsBase + { + protected override float Key => 1; + protected override int Value => 1; + } + public class DictionaryStringKey : DictionaryKeyTestsBase { protected override string Key => "KeyString"; @@ -136,14 +221,14 @@ public Task DoesNotThrowIfEmpty_DeserializeAsync() => JsonSerializer.DeserializeAsync>(new MemoryStream(Encoding.UTF8.GetBytes("{}"))).AsTask(); } - public class DictionaryMyClassKeyUnsupported : DictionaryUnsupportedKeyTestsBase + public class DictionaryMyPublicClassKeyUnsupported : DictionaryUnsupportedKeyTestsBase { - protected override MyClass Key => new MyClass(); + protected override MyPublicClass Key => new MyPublicClass(); } - public class DictionaryMyStructKeyUnsupported : DictionaryUnsupportedKeyTestsBase + public class DictionaryMyPublicStructKeyUnsupported : DictionaryUnsupportedKeyTestsBase { - protected override MyStruct Key => new MyStruct(); + protected override MyPublicStruct Key => new MyPublicStruct(); } public class DictionaryUriKeyUnsupported : DictionaryUnsupportedKeyTestsBase @@ -241,7 +326,6 @@ public async Task ThrowOnInvalidFormatAsync(string json, Type typeToConvert) } [Theory] - [InlineData(@"{""\u0039"":1}", typeof(Dictionary), 9)] [InlineData(@"{""\u0041"":1}", typeof(Dictionary), "A")] [InlineData(@"{""\u0066\u006f\u006f"":1}", typeof(Dictionary), MyEnum.Foo)] [InlineData(@"{""\u0066\u006f\u006f\u002c\u0020\u0062\u0061\u0072"":1}", typeof(Dictionary), MyEnumFlags.Foo | MyEnumFlags.Bar)] @@ -254,17 +338,31 @@ public static void TestUnescapedKeys(string json, Type typeToConvert, object key } [Fact] - public static void TestUnescapedGuidKey() + public static void TestMoreUnescapedKeys() { - // Test Guid which cannot be passed as parameter on above method. - Dictionary result = JsonSerializer.Deserialize>(@"{""\u0036bb67e4e-9780-4895-851b-75f72ac34c5a"":1}"); + // Test types that cannot be passed as InlineData in above method. - Guid myGuid = new Guid("6bb67e4e-9780-4895-851b-75f72ac34c5a"); - Assert.Equal(1, result[myGuid]); + Dictionary result = JsonSerializer.Deserialize>(@"{""\u0036bb67e4e-9780-4895-851b-75f72ac34c5a"":1}"); + Guid guid = new Guid("6bb67e4e-9780-4895-851b-75f72ac34c5a"); + Assert.Equal(1, result[guid]); + + Dictionary result2 = JsonSerializer.Deserialize>(@"{""\u0032\u0030\u0030\u0039\u002d\u0030\u0036\u002d\u0031\u0035\u0054\u0031\u0033\u003a\u0034\u0035\u003a\u0033\u0030\u002e\u0030\u0030\u0030\u0030\u0030\u0030\u0030"":1}"); + // 2009-06-15T13:45:30.0000000 + DateTime dateTime = new DateTime(2009, 6, 15, 13, 45, 30, DateTimeKind.Unspecified); + Assert.Equal(1, result2[dateTime]); + + Dictionary result3 = JsonSerializer.Deserialize>(@"{""\u0032\u0030\u0030\u0039\u002d\u0030\u0036\u002d\u0031\u0035\u0054\u0031\u0033\u003a\u0034\u0035\u003a\u0033\u0030\u002e\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u005a"":1}"); + // 2009-06-15T13:45:30.0000000Z + dateTime = new DateTime(2009, 6, 15, 13, 45, 30, DateTimeKind.Utc); + Assert.Equal(1, result3[dateTime]); + + Dictionary result4 = JsonSerializer.Deserialize>(@"{""\u0032\u0030\u0030\u0039\u002d\u0030\u0036\u002d\u0031\u0035\u0054\u0031\u0033\u003a\u0034\u0035\u003a\u0033\u0030\u002e\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u002d\u0030\u0037\u003a\u0030\u0030"":1}"); + // 2009-06-15T13:45:30.0000000-07:00 + DateTimeOffset dateTimeOffset = new DateTimeOffset(2009, 6, 15, 13, 45, 30, new TimeSpan(-7, 0, 0)); + Assert.Equal(1, result4[dateTimeOffset]); } [Theory] - [InlineData(@"{""\u0039"":1}", typeof(Dictionary), 9)] [InlineData(@"{""\u0041"":1}", typeof(Dictionary), "A")] [InlineData(@"{""\u0066\u006f\u006f"":1}", typeof(Dictionary), MyEnum.Foo)] [InlineData(@"{""\u0066\u006f\u006f\u002c\u0020\u0062\u0061\u0072"":1}", typeof(Dictionary), MyEnumFlags.Foo | MyEnumFlags.Bar)] @@ -280,16 +378,36 @@ public static async Task TestUnescapedKeysAsync(string json, Type typeToConvert, } [Fact] - public static async Task TestUnescapedGuidKeyAsync() + public static async Task TestMoreUnescapedKeyAsync() { - // Test Guid which cannot be passed as parameter on above method. + // Test types that cannot be passed as InlineData in above method. + byte[] utf8Json = Encoding.UTF8.GetBytes(@"{""\u0036bb67e4e-9780-4895-851b-75f72ac34c5a"":1}"); MemoryStream stream = new MemoryStream(utf8Json); - Dictionary result = await JsonSerializer.DeserializeAsync>(stream); - Guid myGuid = new Guid("6bb67e4e-9780-4895-851b-75f72ac34c5a"); Assert.Equal(1, result[myGuid]); + + utf8Json = Encoding.UTF8.GetBytes(@"{""\u0032\u0030\u0030\u0039\u002d\u0030\u0036\u002d\u0031\u0035\u0054\u0031\u0033\u003a\u0034\u0035\u003a\u0033\u0030\u002e\u0030\u0030\u0030\u0030\u0030\u0030\u0030"":1}"); + stream = new MemoryStream(utf8Json); + Dictionary result2 = await JsonSerializer.DeserializeAsync>(stream); + // 2009-06-15T13:45:30.0000000 + DateTime myDate = new DateTime(2009, 6, 15, 13, 45, 30, DateTimeKind.Unspecified); + Assert.Equal(1, result2[myDate]); + + utf8Json = Encoding.UTF8.GetBytes(@"{""\u0032\u0030\u0030\u0039\u002d\u0030\u0036\u002d\u0031\u0035\u0054\u0031\u0033\u003a\u0034\u0035\u003a\u0033\u0030\u002e\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u005a"":1}"); + stream = new MemoryStream(utf8Json); + Dictionary result3 = await JsonSerializer.DeserializeAsync>(stream); + // 2009-06-15T13:45:30.0000000Z + myDate = new DateTime(2009, 6, 15, 13, 45, 30, DateTimeKind.Utc); + Assert.Equal(1, result3[myDate]); + + utf8Json = Encoding.UTF8.GetBytes(@"{""\u0032\u0030\u0030\u0039\u002d\u0030\u0036\u002d\u0031\u0035\u0054\u0031\u0033\u003a\u0034\u0035\u003a\u0033\u0030\u002e\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u002d\u0030\u0037\u003a\u0030\u0030"":1}"); + stream = new MemoryStream(utf8Json); + Dictionary result4 = await JsonSerializer.DeserializeAsync>(stream); + // 2009-06-15T13:45:30.0000000-07:00 + DateTimeOffset dateTimeOffset = new DateTimeOffset(2009, 6, 15, 13, 45, 30, new TimeSpan(-7, 0, 0)); + Assert.Equal(1, result4[dateTimeOffset]); } [Fact] @@ -382,10 +500,6 @@ public async Task TestPolicyOnlyAppliesToStringAsync() } } - public class MyClass { } - - public struct MyStruct { } - public enum MyEnum { Foo, diff --git a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.cs b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.cs index 9dd3c4d4a2fcb..bd16101192e87 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.cs @@ -931,6 +931,10 @@ private interface IClass { } private class MyClass : IClass { } + public class MyPublicClass { } + + public struct MyPublicStruct { } + private class MyNonGenericDictionary : Dictionary { } private class MyFactory : JsonConverterFactory From c272684ce0b1303db9d6c641dfaaa9fa74580b1a Mon Sep 17 00:00:00 2001 From: David Cantu Date: Mon, 22 Jun 2020 17:23:16 -0700 Subject: [PATCH 05/16] Add support for a few more missing types --- .../Text/Json/Reader/Utf8JsonReader.TryGet.cs | 15 ++++++++++++ .../Converters/Value/UInt16Converter.cs | 17 ++++++++++++++ .../Converters/Value/UInt32Converter.cs | 17 ++++++++++++++ .../Converters/Value/UInt64Converter.cs | 17 ++++++++++++++ ...onWriter.WriteProperties.UnsignedNumber.cs | 13 +++++++++++ ...CollectionTests.Dictionary.NonStringKey.cs | 23 +++++++++++++++++++ .../CollectionTests.Dictionary.cs | 4 ---- 7 files changed, 102 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs index 5dc1afbf56053..2ad6575c92d9d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs @@ -672,6 +672,11 @@ public bool TryGetUInt16(out ushort value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } + return TryGetUInt16Core(out value); + } + + internal bool TryGetUInt16Core(out ushort value) + { ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out ushort tmp, out int bytesConsumed) && span.Length == bytesConsumed) @@ -702,6 +707,11 @@ public bool TryGetUInt32(out uint value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } + return TryGetUInt32Core(out value); + } + + internal bool TryGetUInt32Core(out uint value) + { ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out uint tmp, out int bytesConsumed) && span.Length == bytesConsumed) @@ -732,6 +742,11 @@ public bool TryGetUInt64(out ulong value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } + return TryGetUInt64Core(out value); + } + + internal bool TryGetUInt64Core(out ulong value) + { ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out ulong tmp, out int bytesConsumed) && span.Length == bytesConsumed) 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 56f24b06a5345..bf178e63dfcf5 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 @@ -15,5 +15,22 @@ public override void Write(Utf8JsonWriter writer, ushort value, JsonSerializerOp { writer.WriteNumberValue(value); } + + internal override ushort ReadWithQuotes(ref Utf8JsonReader reader) + { + if (!reader.TryGetUInt16Core(out ushort value)) + { + throw ThrowHelper.GetFormatException(NumericType.UInt16); + } + + return value; + } + + internal override void WriteWithQuotes(Utf8JsonWriter writer, ushort value, JsonSerializerOptions options, ref WriteStack state) + { + writer.WritePropertyName(value); + } + + internal override bool CanBeDictionaryKey => true; } } 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 9dfc006ac6836..ecb4554f7d6ad 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 @@ -15,5 +15,22 @@ public override void Write(Utf8JsonWriter writer, uint value, JsonSerializerOpti { writer.WriteNumberValue(value); } + + internal override uint ReadWithQuotes(ref Utf8JsonReader reader) + { + if (!reader.TryGetUInt32Core(out uint value)) + { + throw ThrowHelper.GetFormatException(NumericType.UInt16); + } + + return value; + } + + internal override void WriteWithQuotes(Utf8JsonWriter writer, uint value, JsonSerializerOptions options, ref WriteStack state) + { + writer.WritePropertyName(value); + } + + internal override bool CanBeDictionaryKey => true; } } 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 af26a458c86c9..d8a55b2164ae0 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 @@ -15,5 +15,22 @@ public override void Write(Utf8JsonWriter writer, ulong value, JsonSerializerOpt { writer.WriteNumberValue(value); } + + internal override ulong ReadWithQuotes(ref Utf8JsonReader reader) + { + if (!reader.TryGetUInt64Core(out ulong value)) + { + throw ThrowHelper.GetFormatException(NumericType.UInt64); + } + + return value; + } + + internal override void WriteWithQuotes(Utf8JsonWriter writer, ulong value, JsonSerializerOptions options, ref WriteStack state) + { + writer.WritePropertyName(value); + } + + internal override bool CanBeDictionaryKey => true; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs index 48d9518445434..72ce8408bdff4 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs @@ -441,5 +441,18 @@ private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, ulong v Debug.Assert(result); BytesPending += bytesWritten; } + + internal void WritePropertyName(uint value) + => WritePropertyName((ulong)value); + + internal void WritePropertyName(ulong value) + { + Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatUInt64Length]; + + bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten); + Debug.Assert(result); + + WritePropertyName(utf8PropertyName.Slice(0, bytesWritten)); + } } } 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 c9cd3dc61ac39..72d0b774570e1 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 @@ -179,6 +179,24 @@ public class DictionaryStringKey : DictionaryKeyTestsBase protected override int Value => 1; } + public class DictionaryUInt16Key : DictionaryKeyTestsBase + { + protected override ushort Key => ushort.MaxValue; + protected override int Value => 1; + } + + public class DictionaryUInt32Key : DictionaryKeyTestsBase + { + protected override uint Key => uint.MaxValue; + protected override int Value => 1; + } + + public class DictionaryUInt64Key : DictionaryKeyTestsBase + { + protected override ulong Key => ulong.MaxValue; + protected override int Value => 1; + } + public abstract class DictionaryUnsupportedKeyTestsBase { private Dictionary _dictionary => BuildDictionary(); @@ -500,6 +518,11 @@ public async Task TestPolicyOnlyAppliesToStringAsync() } } + public class MyPublicClass { } + + public struct MyPublicStruct { } + + public enum MyEnum { Foo, diff --git a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.cs b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.cs index bd16101192e87..9dd3c4d4a2fcb 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Dictionary.cs @@ -931,10 +931,6 @@ private interface IClass { } private class MyClass : IClass { } - public class MyPublicClass { } - - public struct MyPublicStruct { } - private class MyNonGenericDictionary : Dictionary { } private class MyFactory : JsonConverterFactory From eb75a968293fa52e0b1140f3a2cd9f36e0683ff7 Mon Sep 17 00:00:00 2001 From: David Cantu Date: Mon, 22 Jun 2020 17:50:51 -0700 Subject: [PATCH 06/16] Add policy support for Enum keys --- .../Converters/Value/EnumConverter.cs | 70 ++++++++++++++++++- ...CollectionTests.Dictionary.NonStringKey.cs | 47 +++++++++++++ 2 files changed, 115 insertions(+), 2 deletions(-) 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 fbc7dc52db1a1..f6307845df7b1 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 @@ -334,8 +334,74 @@ internal override T ReadWithQuotes(ref Utf8JsonReader reader) internal override void WriteWithQuotes(Utf8JsonWriter writer, T value, JsonSerializerOptions options, ref WriteStack state) { - string enumString = value.ToString(); - writer.WritePropertyName(enumString); + ulong key = ConvertToUInt64(value); + + if (_nameCache.TryGetValue(key, out JsonEncodedText formatted)) + { + writer.WritePropertyName(formatted); + return; + } + + string original = value.ToString(); + if (IsValidIdentifier(original)) + { + // We are dealing with a combination of flag constants since + // all constant values were cached during warm-up. + JavaScriptEncoder? encoder = options.Encoder; + + if (_nameCache.Count < NameCacheSizeSoftLimit) + { + formatted = _namingPolicy == null + ? JsonEncodedText.Encode(original, encoder) + : FormatEnumValue(original, encoder); + + writer.WritePropertyName(formatted); + + _nameCache.TryAdd(key, formatted); + } + else + { + // We also do not create a JsonEncodedText instance here because passing the string + // directly to the writer is cheaper than creating one and not caching it for reuse. + writer.WritePropertyName( + _namingPolicy == null + ? original + : FormatEnumValueToString(original, encoder)); + } + + return; + } + + switch (s_enumTypeCode) + { + case TypeCode.Int32: + writer.WritePropertyName(Unsafe.As(ref value)); + break; + case TypeCode.UInt32: + writer.WritePropertyName(Unsafe.As(ref value)); + break; + case TypeCode.UInt64: + writer.WritePropertyName(Unsafe.As(ref value)); + break; + case TypeCode.Int64: + writer.WritePropertyName(Unsafe.As(ref value)); + break; + case TypeCode.Int16: + writer.WritePropertyName(Unsafe.As(ref value)); + break; + case TypeCode.UInt16: + writer.WritePropertyName(Unsafe.As(ref value)); + break; + case TypeCode.Byte: + writer.WritePropertyName(Unsafe.As(ref value)); + break; + case TypeCode.SByte: + writer.WritePropertyName(Unsafe.As(ref value)); + break; + default: + ThrowHelper.ThrowJsonException(); + break; + } } internal override bool CanBeDictionaryKey => true; 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 72d0b774570e1..1a7453234413a 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 @@ -516,6 +516,47 @@ public async Task TestPolicyOnlyAppliesToStringAsync() json = Encoding.UTF8.GetString(stream.ToArray()); Assert.Equal(@"{""1"":1}", json); } + + [Fact] + public void TestEnumKeyWithNamingPolicy() + { + var opts = new JsonSerializerOptions(); + opts.Converters.Add(new JsonStringEnumConverter(new SuffixNamingPolicy())); + + var myEnumIntDictionary = new Dictionary(); + myEnumIntDictionary.Add(MyEnum.Foo, 1); + + string json = JsonSerializer.Serialize(myEnumIntDictionary, opts); + Assert.Equal(@"{""Foo_Suffix"":1}", json); + + var myEnumFlagsIntDictionary = new Dictionary(); + myEnumFlagsIntDictionary.Add(MyEnumFlags.Foo | MyEnumFlags.Bar, 1); + + json = JsonSerializer.Serialize(myEnumFlagsIntDictionary, opts); + Assert.Equal(@"{""Foo_Suffix, Bar_Suffix"":1}", json); + } + + [Fact] + public void TestEnumKeyWithNotValidIdentifier() + { + var myEnumIntDictionary = new Dictionary(); + myEnumIntDictionary.Add((MyEnum)(-1), 1); + + string json = JsonSerializer.Serialize(myEnumIntDictionary); + Assert.Equal(@"{""-1"":1}", json); + + myEnumIntDictionary = JsonSerializer.Deserialize>(json); + Assert.Equal(1, myEnumIntDictionary[(MyEnum)(-1)]); + + var myEnumFlagsIntDictionary = new Dictionary(); + myEnumFlagsIntDictionary.Add((MyEnumFlags)(-1), 1); + + json = JsonSerializer.Serialize(myEnumFlagsIntDictionary); + Assert.Equal(@"{""-1"":1}", json); + + myEnumFlagsIntDictionary = JsonSerializer.Deserialize>(json); + Assert.Equal(1, myEnumFlagsIntDictionary[(MyEnumFlags)(-1)]); + } } public class MyPublicClass { } @@ -563,5 +604,11 @@ public class FixedNamingPolicy : JsonNamingPolicy public const string FixedName = nameof(FixedName); public override string ConvertName(string name) => FixedName; } + + public class SuffixNamingPolicy : JsonNamingPolicy + { + public const string Suffix = "_Suffix"; + public override string ConvertName(string name) => name + Suffix; + } } } From 8d4fd95b1eac3791c59a8fc2ddaf97464b94c783 Mon Sep 17 00:00:00 2001 From: David Cantu Date: Wed, 24 Jun 2020 15:12:32 -0700 Subject: [PATCH 07/16] Address performance regression. --- .../src/Resources/Strings.resx | 5 +- .../Text/Json/Reader/Utf8JsonReader.TryGet.cs | 5 -- .../Collection/DictionaryDefaultConverter.cs | 82 ++++++++----------- .../DictionaryOfTKeyTValueConverter.cs | 4 +- .../Collection/IDictionaryConverter.cs | 11 ++- .../IDictionaryOfTKeyTValueConverter.cs | 4 +- ...ReadOnlyDictionaryOfTKeyTValueConverter.cs | 4 +- ...mmutableDictionaryOfTKeyTValueConverter.cs | 4 +- .../Collection/JsonCollectionConverter.cs | 1 - .../Converters/Object/JsonObjectConverter.cs | 1 - .../Converters/Value/BooleanConverter.cs | 2 - .../Converters/Value/ByteConverter.cs | 2 - .../Converters/Value/CharConverter.cs | 2 - .../Converters/Value/DateTimeConverter.cs | 2 - .../Value/DateTimeOffsetConverter.cs | 2 - .../Converters/Value/DecimalConverter.cs | 2 - .../Converters/Value/DoubleConverter.cs | 2 - .../Converters/Value/EnumConverter.cs | 16 ++-- .../Converters/Value/GuidConverter.cs | 72 ++++++++-------- .../Converters/Value/Int16Converter.cs | 2 - .../Converters/Value/Int32Converter.cs | 4 - .../Converters/Value/Int64Converter.cs | 2 - .../Converters/Value/ObjectConverter.cs | 13 ++- .../Converters/Value/SByteConverter.cs | 6 +- .../Converters/Value/SingleConverter.cs | 6 +- .../Converters/Value/StringConverter.cs | 2 - .../Converters/Value/UInt16Converter.cs | 2 - .../Converters/Value/UInt32Converter.cs | 2 - .../Converters/Value/UInt64Converter.cs | 2 - .../Text/Json/Serialization/JsonClassInfo.cs | 21 ----- .../Text/Json/Serialization/JsonConverter.cs | 4 - .../Serialization/JsonConverterFactory.cs | 2 - .../Json/Serialization/JsonConverterOfT.cs | 2 - .../JsonSerializerOptions.Converters.cs | 62 +++++++++++++- .../Text/Json/ThrowHelper.Serialization.cs | 7 ++ ...CollectionTests.Dictionary.NonStringKey.cs | 19 ----- 36 files changed, 167 insertions(+), 214 deletions(-) diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index de04df8cab1ed..1b15dc7238ab7 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -530,4 +530,7 @@ The JSON value is not in a supported Boolean format. - + + The type '{0}' is not a supported Dictionary key type. + + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs index 2ad6575c92d9d..a06ec515856b3 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs @@ -465,11 +465,6 @@ public bool TryGetBytesFromBase64([NotNullWhen(true)] out byte[]? value) throw ThrowHelper.GetInvalidOperationException_ExpectedString(TokenType); } - return TryGetBytesFromBase64Core(out value); - } - - internal bool TryGetBytesFromBase64Core([NotNullWhen(true)] out byte[]? value) - { ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (_stringHasEscaping) 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 d027f83717d8e..695afc7410c66 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 @@ -17,7 +17,7 @@ internal abstract class DictionaryDefaultConverter /// /// When overridden, adds the value to the collection. /// - protected abstract void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state); + protected abstract void Add(in TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state); /// /// When overridden, converts the temporary collection held in state.Current.ReturnValue to the final collection. @@ -31,7 +31,10 @@ protected virtual void ConvertCollection(ref ReadStack state, JsonSerializerOpti protected virtual void CreateCollection(ref Utf8JsonReader reader, ref ReadStack state) { } internal override Type ElementType => typeof(TValue); - internal override Type KeyType => typeof(TKey); + + protected Type KeyType = typeof(TKey); + // For string keys we don't use a key converter + // in order to avoid performance regression on already supported types. protected bool IsStringKey = typeof(TKey) == typeof(string); protected static JsonConverter GetElementConverter(ref ReadStack state) @@ -50,16 +53,8 @@ protected static JsonConverter GetValueConverter(ref WriteStack state) return converter; } - protected static JsonConverter GetKeyConverter(JsonClassInfo classInfo) - { - var converter = (JsonConverter)classInfo.KeyClassInfo!.PropertyInfoForClassInfo.ConverterBase; - if (!converter.CanBeDictionaryKey) - { - ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(converter.TypeToConvert); - } - - return converter; - } + protected JsonConverter GetKeyConverter(JsonSerializerOptions options) + => (JsonConverter)options.GetDictionaryKeyConverter(KeyType); internal sealed override bool OnTryRead( ref Utf8JsonReader reader, @@ -96,35 +91,27 @@ internal sealed override bool OnTryRead( // Read method would have thrown if otherwise. Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); - if (options.ReferenceHandler != null) - { - ReadOnlySpan propertyName = reader.GetSpan(); - if (propertyName.Length > 0 && propertyName[0] == '$') - { - ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state); - } - } - - JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); - TKey key = keyConverter.ReadWithQuotes(ref reader); - - // Copy key name for JSON Path support in case of error. + TKey key; string unescapedPropertyNameAsString; if (IsStringKey) { - // Special case to avoid calling again GetString() - unescapedPropertyNameAsString = (string)(object)key; + object keyAsObject = reader.GetString()!; + key = (TKey)keyAsObject; + unescapedPropertyNameAsString = (string)keyAsObject; } else { + JsonConverter keyConverter = GetKeyConverter(options); + key = keyConverter.ReadWithQuotes(ref reader); unescapedPropertyNameAsString = reader.GetString()!; } + // Copy key name for JSON Path support in case of error. state.Current.JsonPropertyNameAsString = unescapedPropertyNameAsString; // Read the value and add. reader.ReadWithVerify(); TValue element = elementConverter.Read(ref reader, typeof(TValue), options); - Add(key, element!, options, ref state); + Add(in key, in element!, options, ref state); } } else @@ -143,27 +130,20 @@ internal sealed override bool OnTryRead( // Read method would have thrown if otherwise. Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); - if (options.ReferenceHandler != null) - { - ReadOnlySpan propertyName = reader.GetSpan(); - if (propertyName.Length > 0 && propertyName[0] == '$') - { - ThrowHelper.ThrowUnexpectedMetadataException(propertyName, ref reader, ref state); - } - } - - JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); - TKey key = keyConverter.ReadWithQuotes(ref reader); - // Copy key name for JSON Path support in case of error. string unescapedPropertyNameAsString; + + TKey key; if (IsStringKey) { - // Special case to avoid calling again GetString() - unescapedPropertyNameAsString = (string)(object)key; + object keyAsObject = reader.GetString()!; + key = (TKey)keyAsObject; + unescapedPropertyNameAsString = (string)keyAsObject; } else { + JsonConverter keyConverter = GetKeyConverter(options); + key = keyConverter.ReadWithQuotes(ref reader); unescapedPropertyNameAsString = reader.GetString()!; } @@ -172,7 +152,7 @@ internal sealed override bool OnTryRead( // Get the value from the converter and add it. elementConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValue element); - Add(key, element!, options, ref state); + Add(in key, in element!, options, ref state); } } } @@ -257,7 +237,7 @@ internal sealed override bool OnTryRead( state.Current.PropertyState = StackFramePropertyState.Name; - if (options.ReferenceHandler != null) + if (preserveReferences) { ReadOnlySpan propertyName = reader.GetSpan(); if (propertyName.Length > 0 && propertyName[0] == '$') @@ -266,18 +246,20 @@ internal sealed override bool OnTryRead( } } - JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); - TKey key = keyConverter.ReadWithQuotes(ref reader); - // Copy key name for JSON Path support in case of error. string unescapedPropertyNameAsString; + + TKey key; if (IsStringKey) { - // Special case to avoid calling again GetString() - unescapedPropertyNameAsString = (string)(object)key; + object keyAsObject = reader.GetString()!; + key = (TKey)keyAsObject; + unescapedPropertyNameAsString = (string)keyAsObject; } else { + JsonConverter keyConverter = GetKeyConverter(options); + key = keyConverter.ReadWithQuotes(ref reader); unescapedPropertyNameAsString = reader.GetString()!; } @@ -307,7 +289,7 @@ internal sealed override bool OnTryRead( } TKey key = (TKey)state.Current.DictionaryKey!; - Add(key, element!, options, ref state); + Add(in key, in element!, options, ref state); state.Current.EndElement(); } } 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 e2f27b4d3c53c..f14fb2e0b8e4c 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 @@ -15,7 +15,7 @@ internal sealed class DictionaryOfTKeyTValueConverter where TCollection : Dictionary where TKey : notnull { - protected override void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(in TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) { ((TCollection)state.Current.ReturnValue!)[key] = value; } @@ -50,7 +50,7 @@ protected internal override bool OnWriteResume( enumerator = (Dictionary.Enumerator)state.Current.CollectionEnumerator; } - JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); + JsonConverter keyConverter = GetKeyConverter(options); JsonConverter converter = GetValueConverter(ref state); if (!state.SupportContinuation && converter.CanUseDirectReadOrWrite) { 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 dc30d198628c9..59b0792a828e8 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 @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Generic; +using System.Diagnostics; namespace System.Text.Json.Serialization.Converters { @@ -15,7 +16,7 @@ internal sealed class IDictionaryConverter : DictionaryDefaultConverter where TCollection : IDictionary { - protected override void Add(string key, in object? value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(in string key, in object? value, JsonSerializerOptions options, ref ReadStack state) { ((IDictionary)state.Current.ReturnValue!)[key] = value; } @@ -83,15 +84,17 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio // Optimize for string since that's the hot path. if (key is string keyString) { - JsonConverter stringKeyConverter = GetKeyConverter(state.Current.JsonClassInfo); + JsonConverter stringKeyConverter = GetKeyConverter(options); stringKeyConverter.WriteWithQuotes(writer, keyString, options, ref state); } else { // IDictionary is a spacial case since it has polymorphic object semantics on serialization // but needs to use JsonConverter on deserialization. - JsonClassInfo classInfo = options.GetOrAddClass(typeof(object)); - JsonConverter objectKeyConverter = (JsonConverter)classInfo.PropertyInfoForClassInfo.ConverterBase; + JsonConverter? keyConverter = options.GetDictionaryKeyConverter(typeof(object)); + Debug.Assert(keyConverter != null); + + JsonConverter objectKeyConverter = (JsonConverter)keyConverter; objectKeyConverter.WriteWithQuotes(writer, key, options, ref state); } } 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 5e2abe152c7bb..27971fb950be1 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 @@ -15,7 +15,7 @@ internal sealed class IDictionaryOfTKeyTValueConverter where TKey : notnull { - protected override void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(in TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) { ((TCollection)state.Current.ReturnValue!)[key] = value; } @@ -71,7 +71,7 @@ protected internal override bool OnWriteResume( enumerator = (IEnumerator>)state.Current.CollectionEnumerator; } - JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); + JsonConverter keyConverter = GetKeyConverter(options); JsonConverter converter = GetValueConverter(ref state); do { 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 8ed53da05d301..598e8aa671deb 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 @@ -11,7 +11,7 @@ internal sealed class IReadOnlyDictionaryOfTKeyTValueConverter where TKey : notnull { - protected override void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(in TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) { ((Dictionary)state.Current.ReturnValue!)[key] = value; } @@ -42,7 +42,7 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio enumerator = (Dictionary.Enumerator)state.Current.CollectionEnumerator; } - JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); + JsonConverter keyConverter = GetKeyConverter(options); JsonConverter converter = GetValueConverter(ref state); do { 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 dc7fc583f9738..e86b74536ac53 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 @@ -11,7 +11,7 @@ internal sealed class ImmutableDictionaryOfTKeyTValueConverter where TKey : notnull { - protected override void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(in TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) { ((Dictionary)state.Current.ReturnValue!)[key] = value; } @@ -53,7 +53,7 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio enumerator = (IEnumerator>)state.Current.CollectionEnumerator; } - JsonConverter keyConverter = GetKeyConverter(state.Current.JsonClassInfo); + JsonConverter keyConverter = GetKeyConverter(options); JsonConverter converter = GetValueConverter(ref state); do { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonCollectionConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonCollectionConverter.cs index 8194f9bd86ab0..a8c51b8cc7b01 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonCollectionConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonCollectionConverter.cs @@ -11,6 +11,5 @@ internal abstract class JsonCollectionConverter : JsonRes { internal sealed override ClassType ClassType => ClassType.Enumerable; internal override Type ElementType => typeof(TElement); - internal sealed override Type? KeyType => null; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/JsonObjectConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/JsonObjectConverter.cs index 73f0248992a9d..433cb307704af 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/JsonObjectConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/JsonObjectConverter.cs @@ -12,6 +12,5 @@ internal abstract class JsonObjectConverter : JsonResumableConverter { internal sealed override ClassType ClassType => ClassType.Object; internal sealed override Type? ElementType => null; - internal sealed override Type? KeyType => null; } } 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 0f76cee033d3e..32aba9afcf3ff 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 @@ -34,7 +34,5 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, bool value, JsonSe { writer.WritePropertyName(value); } - - internal override bool CanBeDictionaryKey => true; } } 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 e40a2518a9f78..b9a6f960fc8e9 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 @@ -30,7 +30,5 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, byte value, JsonSe { writer.WritePropertyName(value); } - - internal override bool CanBeDictionaryKey => true; } } 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 07968219b4403..bfc9a72c4062d 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 @@ -49,7 +49,5 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, char value, JsonSe #endif ); } - - internal override bool CanBeDictionaryKey => true; } } 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 a55a6cb213c25..9a63650785579 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 @@ -30,7 +30,5 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, DateTime value, Js { writer.WritePropertyName(value); } - - internal override bool CanBeDictionaryKey => true; } } 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 02c0a946a736b..528022c09f6eb 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 @@ -30,7 +30,5 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, DateTimeOffset val { writer.WritePropertyName(value); } - - internal override bool CanBeDictionaryKey => true; } } 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 14d12c85a122c..34e8a41dbf7aa 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 @@ -30,7 +30,5 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, decimal value, Jso { writer.WritePropertyName(value); } - - internal override bool CanBeDictionaryKey => true; } } 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 67ff41c7852ed..401ec768d3829 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 @@ -30,7 +30,5 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, double value, Json { writer.WritePropertyName(value); } - - internal override bool CanBeDictionaryKey => true; } } 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 f6307845df7b1..77eb78dc3f355 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 @@ -334,6 +334,11 @@ internal override T ReadWithQuotes(ref Utf8JsonReader reader) internal override void WriteWithQuotes(Utf8JsonWriter writer, T value, JsonSerializerOptions options, ref WriteStack state) { + // An EnumConverter that invokes this method + // can only be created by JsonSerializerOptions.GetDictionaryKeyConverter + // hence no naming policy is expected. + Debug.Assert(_namingPolicy == null); + ulong key = ConvertToUInt64(value); if (_nameCache.TryGetValue(key, out JsonEncodedText formatted)) @@ -351,9 +356,7 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, T value, JsonSeria if (_nameCache.Count < NameCacheSizeSoftLimit) { - formatted = _namingPolicy == null - ? JsonEncodedText.Encode(original, encoder) - : FormatEnumValue(original, encoder); + formatted = JsonEncodedText.Encode(original, encoder); writer.WritePropertyName(formatted); @@ -363,10 +366,7 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, T value, JsonSeria { // We also do not create a JsonEncodedText instance here because passing the string // directly to the writer is cheaper than creating one and not caching it for reuse. - writer.WritePropertyName( - _namingPolicy == null - ? original - : FormatEnumValueToString(original, encoder)); + writer.WritePropertyName(original); } return; @@ -403,7 +403,5 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, T value, JsonSeria break; } } - - internal override bool CanBeDictionaryKey => true; } } 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 7b6ae9ca88d9b..139f70488b14a 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 @@ -1,38 +1,34 @@ -// 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.Buffers.Text; - -namespace System.Text.Json.Serialization.Converters -{ - internal sealed class GuidConverter : JsonConverter - { - public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return reader.GetGuid(); - } - - public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options) - { - writer.WriteStringValue(value); - } - - internal override Guid ReadWithQuotes(ref Utf8JsonReader reader) - { - if (!reader.TryGetGuidCore(out Guid value)) - { - throw ThrowHelper.GetFormatException(DataType.Guid); - } - - return value; - } - - internal override void WriteWithQuotes(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options, ref WriteStack state) - { - writer.WritePropertyName(value); - } - - internal override bool CanBeDictionaryKey => true; - } -} +// 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. + +namespace System.Text.Json.Serialization.Converters +{ + internal sealed class GuidConverter : JsonConverter + { + public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetGuid(); + } + + public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options) + { + writer.WriteStringValue(value); + } + + internal override Guid ReadWithQuotes(ref Utf8JsonReader reader) + { + if (!reader.TryGetGuidCore(out Guid value)) + { + throw ThrowHelper.GetFormatException(DataType.Guid); + } + + return value; + } + + internal override void WriteWithQuotes(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 ea5bc612b962c..3f8d5782735ab 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 @@ -32,7 +32,5 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, [DisallowNull] sho { writer.WritePropertyName(value); } - - internal override bool CanBeDictionaryKey => true; } } 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 db1ed78a6a989..0de33c225f4d0 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 @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Buffers.Text; - namespace System.Text.Json.Serialization.Converters { internal sealed class Int32Converter : JsonConverter @@ -32,7 +30,5 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, int value, JsonSer { writer.WritePropertyName(value); } - - internal override bool CanBeDictionaryKey => true; } } 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 623099520552a..8384e0c96f9c4 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 @@ -30,7 +30,5 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, long value, JsonSe { writer.WritePropertyName(value); } - - internal override bool CanBeDictionaryKey => true; } } 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 cd4dbbe7cf51e..fe680f6cde506 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,22 +24,19 @@ internal override object ReadWithQuotes(ref Utf8JsonReader reader) internal override void WriteWithQuotes(Utf8JsonWriter writer, object value, JsonSerializerOptions options, ref WriteStack state) { - JsonConverter runtimeConverter = GetRuntimeConverter(value.GetType(), state.Current.JsonClassInfo.Type, options); + JsonConverter runtimeConverter = GetRuntimeConverter(value.GetType(), options); runtimeConverter.WriteWithQuotesAsObject(writer, value, options, ref state); } - private JsonConverter GetRuntimeConverter(Type runtimeType, Type parentType, JsonSerializerOptions options) + private JsonConverter GetRuntimeConverter(Type runtimeType, JsonSerializerOptions options) { - JsonConverter runtimeConverter = options.GetOrAddClass(runtimeType).PropertyInfoForClassInfo.ConverterBase; - - if (runtimeConverter == this || !runtimeConverter.CanBeDictionaryKey) + JsonConverter runtimeConverter = options.GetDictionaryKeyConverter(runtimeType); + if (runtimeConverter == this) { - ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(parentType); + ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(runtimeType); } return runtimeConverter; } - - internal override bool CanBeDictionaryKey => true; } } 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 36b9dd157a1f1..af8e777565f6f 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 @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Diagnostics.CodeAnalysis; - namespace System.Text.Json.Serialization.Converters { internal sealed class SByteConverter : JsonConverter @@ -28,11 +26,9 @@ internal override sbyte ReadWithQuotes(ref Utf8JsonReader reader) return value; } - internal override void WriteWithQuotes(Utf8JsonWriter writer, [DisallowNull] sbyte value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteWithQuotes(Utf8JsonWriter writer, sbyte value, JsonSerializerOptions options, ref WriteStack state) { writer.WritePropertyName(value); } - - internal override bool CanBeDictionaryKey => true; } } 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 14e82af8fae9f..57d62732b8169 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 @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Diagnostics.CodeAnalysis; - namespace System.Text.Json.Serialization.Converters { internal sealed class SingleConverter : JsonConverter @@ -28,11 +26,9 @@ internal override float ReadWithQuotes(ref Utf8JsonReader reader) return value; } - internal override void WriteWithQuotes(Utf8JsonWriter writer, [DisallowNull] float value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteWithQuotes(Utf8JsonWriter writer, float value, JsonSerializerOptions options, ref WriteStack state) { writer.WritePropertyName(value); } - - internal override bool CanBeDictionaryKey => true; } } 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 110855d27f47c..337128016c037 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 @@ -37,7 +37,5 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, [DisallowNull] str writer.WritePropertyName(value); } - - internal override bool CanBeDictionaryKey => true; } } 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 bf178e63dfcf5..dd6d49118443d 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 @@ -30,7 +30,5 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, ushort value, Json { writer.WritePropertyName(value); } - - internal override bool CanBeDictionaryKey => true; } } 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 ecb4554f7d6ad..5e49916aa0a88 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 @@ -30,7 +30,5 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, uint value, JsonSe { writer.WritePropertyName(value); } - - internal override bool CanBeDictionaryKey => true; } } 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 d8a55b2164ae0..5233f908484c4 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 @@ -30,7 +30,5 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, ulong value, JsonS { writer.WritePropertyName(value); } - - internal override bool CanBeDictionaryKey => true; } } 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 f383a75769668..22bc9e32bdaf7 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 @@ -29,9 +29,6 @@ internal sealed partial class JsonClassInfo // If enumerable, the JsonClassInfo for the element type. private JsonClassInfo? _elementClassInfo; - // If dictionary, the JsonClassInfo for the key type. - private JsonClassInfo? _keyClassInfo; - /// /// Return the JsonClassInfo for the element type, or null if the type is not an enumerable or dictionary. /// @@ -55,25 +52,8 @@ public JsonClassInfo? ElementClassInfo } } - public JsonClassInfo? KeyClassInfo - { - get - { - if (_keyClassInfo == null && KeyType != null) - { - Debug.Assert(ClassType == ClassType.Dictionary); - // TODO: When looking for the key converter, only consider types with an internal converter. - _keyClassInfo = Options.GetOrAddClass(KeyType); - } - - return _keyClassInfo; - } - } - public Type? ElementType { get; set; } - public Type? KeyType { get; set; } - public JsonSerializerOptions Options { get; private set; } public Type Type { get; private set; } @@ -226,7 +206,6 @@ public JsonClassInfo(Type type, JsonSerializerOptions options) case ClassType.Enumerable: case ClassType.Dictionary: { - KeyType = converter.KeyType; ElementType = converter.ElementType; CreateObject = options.MemberAccessorStrategy.CreateConstructor(runtimeType); } 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 ee4ef7bb9eee6..25b43c861342a 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 @@ -32,8 +32,6 @@ internal JsonConverter() { } /// internal virtual bool CanHaveIdMetadata => true; - internal abstract bool CanBeDictionaryKey { get; } - internal bool CanBePolymorphic { get; set; } internal abstract JsonPropertyInfo CreateJsonPropertyInfo(); @@ -42,8 +40,6 @@ internal JsonConverter() { } internal abstract Type? ElementType { get; } - internal abstract Type? KeyType { get; } - /// /// Cached value of TypeToConvert.IsValueType, which is an expensive call. /// 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 55f717ee7a7c8..0250731debbfc 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 @@ -53,8 +53,6 @@ internal override JsonParameterInfo CreateJsonParameterInfo() } internal sealed override Type? ElementType => null; - internal sealed override Type? KeyType => null; - internal sealed override bool CanBeDictionaryKey => false; internal JsonConverter GetConverterInternal(Type typeToConvert, JsonSerializerOptions options) { 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 a3f1f432654b3..8932d208e4151 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 @@ -56,8 +56,6 @@ internal override sealed JsonParameterInfo CreateJsonParameterInfo() } internal override Type? ElementType => null; - internal override Type? KeyType => null; - internal override bool CanBeDictionaryKey => false; /// /// Indicates whether should be passed to the converter on serialization, 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 1d757ef80ce6b..bb879054d37b1 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 @@ -71,7 +71,67 @@ private static Dictionary GetDefaultSimpleConverters() return converters; void Add(JsonConverter converter) => - converters.Add(converter.TypeToConvert!, converter); + converters.Add(converter.TypeToConvert, converter); + } + + internal JsonConverter GetDictionaryKeyConverter(Type keyType) + { + if (!_dictionaryKeyConverters.TryGetValue(keyType, out JsonConverter? converter)){ + if (keyType.IsEnum) + { + converter = GetEnumConverter(); + _dictionaryKeyConverters.Add(keyType, converter); + } + else + { + ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(keyType); + } + } + + return converter!; + + JsonConverter GetEnumConverter() + => (JsonConverter)Activator.CreateInstance( + typeof(EnumConverter<>).MakeGenericType(keyType), + BindingFlags.Instance | BindingFlags.Public, + binder: null, + new object[] { EnumConverterOptions.AllowStrings | EnumConverterOptions.AllowNumbers, this }, + culture: null)!; + } + + private readonly Dictionary _dictionaryKeyConverters = GetDictionaryKeyConverters(); + + private static Dictionary GetDictionaryKeyConverters() + { + const int NumberOfConverters = 18; + var converters = new Dictionary(NumberOfConverters); + + // When adding to this, update NumberOfConverters above. + Add(new BooleanConverter()); + Add(new ByteConverter()); + Add(new CharConverter()); + Add(new DateTimeConverter()); + Add(new DateTimeOffsetConverter()); + Add(new DoubleConverter()); + Add(new DecimalConverter()); + Add(new GuidConverter()); + Add(new Int16Converter()); + Add(new Int32Converter()); + Add(new Int64Converter()); + Add(new ObjectConverter()); + Add(new SByteConverter()); + Add(new SingleConverter()); + Add(new StringConverter()); + Add(new UInt16Converter()); + Add(new UInt32Converter()); + Add(new UInt64Converter()); + + Debug.Assert(NumberOfConverters == converters.Count); + + return converters; + + void Add(JsonConverter converter) => + converters.Add(converter.TypeToConvert, converter); } /// 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 71d3f2ada0f49..a27c198c97e01 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 @@ -40,6 +40,13 @@ public static void ThrowNotSupportedException_ConstructorMaxOf64Parameters(Const throw new NotSupportedException(SR.Format(SR.ConstructorMaxOf64Parameters, constructorInfo, type)); } + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowNotSupportedException_DictionaryKeyTypeNotSupported(Type keyType) + { + throw new NotSupportedException(SR.Format(SR.DictionaryKeyTypeNotSupported, keyType)); + } + [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType) 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 1a7453234413a..d684f82b25e5e 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 @@ -517,25 +517,6 @@ public async Task TestPolicyOnlyAppliesToStringAsync() Assert.Equal(@"{""1"":1}", json); } - [Fact] - public void TestEnumKeyWithNamingPolicy() - { - var opts = new JsonSerializerOptions(); - opts.Converters.Add(new JsonStringEnumConverter(new SuffixNamingPolicy())); - - var myEnumIntDictionary = new Dictionary(); - myEnumIntDictionary.Add(MyEnum.Foo, 1); - - string json = JsonSerializer.Serialize(myEnumIntDictionary, opts); - Assert.Equal(@"{""Foo_Suffix"":1}", json); - - var myEnumFlagsIntDictionary = new Dictionary(); - myEnumFlagsIntDictionary.Add(MyEnumFlags.Foo | MyEnumFlags.Bar, 1); - - json = JsonSerializer.Serialize(myEnumFlagsIntDictionary, opts); - Assert.Equal(@"{""Foo_Suffix, Bar_Suffix"":1}", json); - } - [Fact] public void TestEnumKeyWithNotValidIdentifier() { From f9b507c1702d59ebee0fea892bef18548f77dd3a Mon Sep 17 00:00:00 2001 From: David Cantu Date: Wed, 24 Jun 2020 18:27:40 -0700 Subject: [PATCH 08/16] Fix test error on netstandard --- .../Serialization/PropertyVisibilityTests.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs index a5f4840c5231a..8fb2731ae7876 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs @@ -851,38 +851,41 @@ public static void JsonIgnoreAttribute_UnsupportedCollection() JsonSerializerOptions options = new JsonSerializerOptions(); // Unsupported collections will throw on serialize by default. - // Only if they contain elements. - List> elements = new List>(); + // Only when the collection contains elements. + + var dictionary = new Dictionary(); // Uri is an unsupported dictionary key. - elements.Add(new KeyValuePair(new Uri("http://foo"), "bar")); + dictionary.Add(new Uri("http://foo"), "bar"); - var concurrent = new ConcurrentDictionary(elements); - var dictionary = new Dictionary(elements); + var concurrentDictionary = new ConcurrentDictionary(dictionary); var instance = new ClassWithUnsupportedDictionary() { - MyConcurrentDict = concurrent, + MyConcurrentDict = concurrentDictionary, MyIDict = dictionary }; var instanceWithIgnore = new ClassWithIgnoredUnsupportedDictionary { - MyConcurrentDict = concurrent, + MyConcurrentDict = concurrentDictionary, MyIDict = dictionary }; Assert.Throws(() => JsonSerializer.Serialize(instance, options)); - // Unsupported collections will throw on deserialize by default. + // Unsupported collections will throw on deserialize by default if they contain elements. options = new JsonSerializerOptions(); Assert.Throws(() => JsonSerializer.Deserialize(wrapperJson, options)); options = new JsonSerializerOptions(); - // Unsupported collections will throw on serialize by default. + // Unsupported collections will throw on serialize by default if they contain elements. Assert.Throws(() => JsonSerializer.Serialize(instance, options)); // When ignored, we can serialize and deserialize without exceptions. options = new JsonSerializerOptions(); + + Assert.NotNull(JsonSerializer.Serialize(instanceWithIgnore, options)); + ClassWithIgnoredUnsupportedDictionary obj = JsonSerializer.Deserialize(json, options); Assert.Null(obj.MyDict); From 578138e1d8b81b608e051dbd4ff58969a214c356 Mon Sep 17 00:00:00 2001 From: David Cantu Date: Tue, 30 Jun 2020 03:09:40 -0700 Subject: [PATCH 09/16] 1. Add support for escaped characters on Read and avoid escaping on Write 2. Address some feedback --- .../src/Resources/Strings.resx | 2 +- .../Text/Json/Reader/Utf8JsonReader.TryGet.cs | 218 +++++++++++++++--- .../System/Text/Json/Reader/Utf8JsonReader.cs | 13 ++ .../Collection/DictionaryDefaultConverter.cs | 51 ++-- .../DictionaryOfTKeyTValueConverter.cs | 4 +- .../Collection/IDictionaryConverter.cs | 16 +- .../IDictionaryOfTKeyTValueConverter.cs | 4 +- .../Collection/IEnumerableConverterFactory.cs | 6 +- ...ReadOnlyDictionaryOfTKeyTValueConverter.cs | 4 +- ...mmutableDictionaryOfTKeyTValueConverter.cs | 4 +- .../Converters/Value/ByteConverter.cs | 7 +- .../Converters/Value/CharConverter.cs | 9 +- .../Converters/Value/DateTimeConverter.cs | 7 +- .../Value/DateTimeOffsetConverter.cs | 7 +- .../Converters/Value/DecimalConverter.cs | 7 +- .../Converters/Value/DoubleConverter.cs | 7 +- .../Converters/Value/EnumConverter.cs | 12 +- .../Converters/Value/GuidConverter.cs | 63 +++-- .../Converters/Value/Int16Converter.cs | 9 +- .../Converters/Value/Int32Converter.cs | 7 +- .../Converters/Value/Int64Converter.cs | 7 +- .../Converters/Value/SByteConverter.cs | 7 +- .../Converters/Value/SingleConverter.cs | 7 +- .../Converters/Value/StringConverter.cs | 4 +- .../Converters/Value/UInt16Converter.cs | 7 +- .../Converters/Value/UInt32Converter.cs | 7 +- .../Converters/Value/UInt64Converter.cs | 7 +- .../JsonSerializerOptions.Converters.cs | 15 +- ...Utf8JsonWriter.WriteProperties.DateTime.cs | 2 +- ...onWriter.WriteProperties.DateTimeOffset.cs | 2 +- .../Utf8JsonWriter.WriteProperties.Decimal.cs | 2 +- .../Utf8JsonWriter.WriteProperties.Double.cs | 2 +- .../Utf8JsonWriter.WriteProperties.Float.cs | 2 +- .../Utf8JsonWriter.WriteProperties.Guid.cs | 2 +- .../Utf8JsonWriter.WriteProperties.Literal.cs | 2 +- ...JsonWriter.WriteProperties.SignedNumber.cs | 2 +- .../Utf8JsonWriter.WriteProperties.String.cs | 9 + ...onWriter.WriteProperties.UnsignedNumber.cs | 2 +- ...CollectionTests.Dictionary.NonStringKey.cs | 98 ++++++-- 39 files changed, 390 insertions(+), 253 deletions(-) diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index 1b15dc7238ab7..7309f577181ed 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -533,4 +533,4 @@ The type '{0}' is not a supported Dictionary key type. - \ No newline at end of file + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs index a06ec515856b3..05bdb807b689e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs @@ -6,6 +6,7 @@ using System.Buffers.Text; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; namespace System.Text.Json { @@ -138,6 +139,16 @@ public byte GetByte() return value; } + internal byte GetByteWithQuotes() + { + ReadOnlySpan span = GetUnescapedSpan(); + if (!TryGetByteCore(out byte value, span)) + { + throw ThrowHelper.GetFormatException(NumericType.Byte); + } + return value; + } + /// /// Parses the current JSON token value from the source as an . /// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to an @@ -163,6 +174,16 @@ public sbyte GetSByte() return value; } + internal sbyte GetSByteWithQuotes() + { + ReadOnlySpan span = GetUnescapedSpan(); + if (!TryGetSByteCore(out sbyte value, span)) + { + throw ThrowHelper.GetFormatException(NumericType.SByte); + } + return value; + } + /// /// Parses the current JSON token value from the source as a . /// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a @@ -187,6 +208,16 @@ public short GetInt16() return value; } + internal short GetInt16WithQuotes() + { + ReadOnlySpan span = GetUnescapedSpan(); + if (!TryGetInt16Core(out short value, span)) + { + throw ThrowHelper.GetFormatException(NumericType.Int16); + } + return value; + } + /// /// Parses the current JSON token value from the source as an . /// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to an @@ -211,6 +242,16 @@ public int GetInt32() return value; } + internal int GetInt32WithQuotes() + { + ReadOnlySpan span = GetUnescapedSpan(); + if (!TryGetInt32Core(out int value, span)) + { + throw ThrowHelper.GetFormatException(NumericType.Int32); + } + return value; + } + /// /// Parses the current JSON token value from the source as a . /// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a @@ -235,6 +276,16 @@ public long GetInt64() return value; } + internal long GetInt64WithQuotes() + { + ReadOnlySpan span = GetUnescapedSpan(); + if (!TryGetInt64Core(out long value, span)) + { + throw ThrowHelper.GetFormatException(NumericType.Int64); + } + return value; + } + /// /// Parses the current JSON token value from the source as a . /// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a @@ -260,6 +311,16 @@ public ushort GetUInt16() return value; } + internal ushort GetUInt16WithQuotes() + { + ReadOnlySpan span = GetUnescapedSpan(); + if (!TryGetUInt16Core(out ushort value, span)) + { + throw ThrowHelper.GetFormatException(NumericType.UInt16); + } + return value; + } + /// /// Parses the current JSON token value from the source as a . /// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a @@ -285,6 +346,16 @@ public uint GetUInt32() return value; } + internal uint GetUInt32WithQuotes() + { + ReadOnlySpan span = GetUnescapedSpan(); + if (!TryGetUInt32Core(out uint value, span)) + { + throw ThrowHelper.GetFormatException(NumericType.UInt32); + } + return value; + } + /// /// Parses the current JSON token value from the source as a . /// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a @@ -310,6 +381,16 @@ public ulong GetUInt64() return value; } + internal ulong GetUInt64WithQuotes() + { + ReadOnlySpan span = GetUnescapedSpan(); + if (!TryGetUInt64Core(out ulong value, span)) + { + throw ThrowHelper.GetFormatException(NumericType.UInt64); + } + return value; + } + /// /// Parses the current JSON token value from the source as a . /// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a @@ -333,6 +414,16 @@ public float GetSingle() return value; } + internal float GetSingleWithQuotes() + { + ReadOnlySpan span = GetUnescapedSpan(); + if (!TryGetSingleCore(out float value, span)) + { + throw ThrowHelper.GetFormatException(NumericType.Single); + } + return value; + } + /// /// Parses the current JSON token value from the source as a . /// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a @@ -356,6 +447,16 @@ public double GetDouble() return value; } + internal double GetDoubleWithQuotes() + { + ReadOnlySpan span = GetUnescapedSpan(); + if (!TryGetDoubleCore(out double value, span)) + { + throw ThrowHelper.GetFormatException(NumericType.Double); + } + return value; + } + /// /// Parses the current JSON token value from the source as a . /// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a @@ -379,6 +480,16 @@ public decimal GetDecimal() return value; } + internal decimal GetDecimalWithQuotes() + { + ReadOnlySpan span = GetUnescapedSpan(); + if (!TryGetDecimalCore(out decimal value, span)) + { + throw ThrowHelper.GetFormatException(NumericType.Decimal); + } + return value; + } + /// /// Parses the current JSON token value from the source as a . /// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a @@ -402,6 +513,16 @@ public DateTime GetDateTime() return value; } + internal DateTime GetDateTimeNoValidation() + { + if (!TryGetDateTimeCore(out DateTime value)) + { + throw ThrowHelper.GetFormatException(DataType.DateTime); + } + + return value; + } + /// /// Parses the current JSON token value from the source as a . /// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a @@ -425,6 +546,16 @@ public DateTimeOffset GetDateTimeOffset() return value; } + internal DateTimeOffset GetDateTimeOffsetNoValidation() + { + if (!TryGetDateTimeOffsetCore(out DateTimeOffset value)) + { + throw ThrowHelper.GetFormatException(DataType.DateTimeOffset); + } + + return value; + } + /// /// Parses the current JSON token value from the source as a . /// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to a @@ -448,6 +579,16 @@ public Guid GetGuid() return value; } + internal Guid GetGuidNoValidation() + { + if (!TryGetGuidCore(out Guid value)) + { + throw ThrowHelper.GetFormatException(DataType.Guid); + } + + return value; + } + /// /// Parses the current JSON token value from the source and decodes the Base64 encoded JSON string as bytes. /// Returns if the entire token value is encoded as valid Base64 text and can be successfully @@ -495,12 +636,13 @@ public bool TryGetByte(out byte value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } - return TryGetByteCore(out value); + ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetByteCore(out value, span); } - internal bool TryGetByteCore(out byte value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetByteCore(out byte value, ReadOnlySpan span) { - ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out byte tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -530,12 +672,13 @@ public bool TryGetSByte(out sbyte value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } - return TryGetSByteCore(out value); + ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetSByteCore(out value, span); } - internal bool TryGetSByteCore(out sbyte value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetSByteCore(out sbyte value, ReadOnlySpan span) { - ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out sbyte tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -564,12 +707,13 @@ public bool TryGetInt16(out short value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } - return TryGetInt16Core(out value); + ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetInt16Core(out value, span); } - internal bool TryGetInt16Core(out short value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetInt16Core(out short value, ReadOnlySpan span) { - ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out short tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -598,12 +742,13 @@ public bool TryGetInt32(out int value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } - return TryGetInt32Core(out value); + ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetInt32Core(out value, span); } - internal bool TryGetInt32Core(out int value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetInt32Core(out int value, ReadOnlySpan span) { - ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out int tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -632,12 +777,13 @@ public bool TryGetInt64(out long value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } - return TryGetInt64Core(out value); + ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetInt64Core(out value, span); } - internal bool TryGetInt64Core(out long value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetInt64Core(out long value, ReadOnlySpan span) { - ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out long tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -667,12 +813,13 @@ public bool TryGetUInt16(out ushort value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } - return TryGetUInt16Core(out value); + ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetUInt16Core(out value, span); } - internal bool TryGetUInt16Core(out ushort value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetUInt16Core(out ushort value, ReadOnlySpan span) { - ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out ushort tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -702,12 +849,13 @@ public bool TryGetUInt32(out uint value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } - return TryGetUInt32Core(out value); + ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetUInt32Core(out value, span); } - internal bool TryGetUInt32Core(out uint value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetUInt32Core(out uint value, ReadOnlySpan span) { - ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out uint tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -737,12 +885,13 @@ public bool TryGetUInt64(out ulong value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } - return TryGetUInt64Core(out value); + ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetUInt64Core(out value, span); } - internal bool TryGetUInt64Core(out ulong value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetUInt64Core(out ulong value, ReadOnlySpan span) { - ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out ulong tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -771,12 +920,13 @@ public bool TryGetSingle(out float value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } - return TryGetSingleCore(out value); + ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetSingleCore(out value, span); } - internal bool TryGetSingleCore(out float value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetSingleCore(out float value, ReadOnlySpan span) { - ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out float tmp, out int bytesConsumed, _numberFormat) && span.Length == bytesConsumed) { @@ -805,12 +955,13 @@ public bool TryGetDouble(out double value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } - return TryGetDoubleCore(out value); + ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetDoubleCore(out value, span); } - internal bool TryGetDoubleCore(out double value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetDoubleCore(out double value, ReadOnlySpan span) { - ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out double tmp, out int bytesConsumed, _numberFormat) && span.Length == bytesConsumed) { @@ -839,12 +990,13 @@ public bool TryGetDecimal(out decimal value) throw ThrowHelper.GetInvalidOperationException_ExpectedNumber(TokenType); } - return TryGetDecimalCore(out value); + ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetDecimalCore(out value, span); } - internal bool TryGetDecimalCore(out decimal value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetDecimalCore(out decimal value, ReadOnlySpan span) { - ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; if (Utf8Parser.TryParse(span, out decimal tmp, out int bytesConsumed, _numberFormat) && span.Length == bytesConsumed) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs index cb43f7b936049..b9d2723495137 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs @@ -2552,5 +2552,18 @@ private string DebugTokenType JsonTokenType.True => nameof(JsonTokenType.True), _ => ((byte)TokenType).ToString() }; + + private ReadOnlySpan GetUnescapedSpan() + { + ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + if (_stringHasEscaping) + { + int idx = span.IndexOf(JsonConstants.BackSlash); + Debug.Assert(idx != -1); + span = JsonReaderHelper.GetUnescapedSpan(span, idx); + } + + return span; + } } } 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 695afc7410c66..8d38ef9241eb8 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 @@ -17,7 +17,7 @@ internal abstract class DictionaryDefaultConverter /// /// When overridden, adds the value to the collection. /// - protected abstract void Add(in TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state); + protected abstract void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state); /// /// When overridden, converts the temporary collection held in state.Current.ReturnValue to the final collection. @@ -37,24 +37,18 @@ protected virtual void CreateCollection(ref Utf8JsonReader reader, ref ReadStack // in order to avoid performance regression on already supported types. protected bool IsStringKey = typeof(TKey) == typeof(string); - protected static JsonConverter GetElementConverter(ref ReadStack state) - { - JsonConverter converter = (JsonConverter)state.Current.JsonClassInfo.ElementClassInfo!.PropertyInfoForClassInfo.ConverterBase; - Debug.Assert(converter != null); // It should not be possible to have a null converter at this point. - - return converter; - } + private JsonConverter? _keyConverter; - protected static JsonConverter GetValueConverter(ref WriteStack state) + protected static JsonConverter GetValueConverter(JsonClassInfo classInfo) { - JsonConverter converter = (JsonConverter)state.Current.JsonClassInfo.ElementClassInfo!.PropertyInfoForClassInfo.ConverterBase; + JsonConverter converter = (JsonConverter)classInfo.ElementClassInfo!.PropertyInfoForClassInfo.ConverterBase; Debug.Assert(converter != null); // It should not be possible to have a null converter at this point. return converter; } protected JsonConverter GetKeyConverter(JsonSerializerOptions options) - => (JsonConverter)options.GetDictionaryKeyConverter(KeyType); + => _keyConverter ??= (JsonConverter)options.GetDictionaryKeyConverter(KeyType); internal sealed override bool OnTryRead( ref Utf8JsonReader reader, @@ -74,7 +68,7 @@ internal sealed override bool OnTryRead( CreateCollection(ref reader, ref state); - JsonConverter elementConverter = GetElementConverter(ref state); + JsonConverter elementConverter = GetValueConverter(state.Current.JsonClassInfo); if (elementConverter.CanUseDirectReadOrWrite) { // Process all elements. @@ -95,9 +89,8 @@ internal sealed override bool OnTryRead( string unescapedPropertyNameAsString; if (IsStringKey) { - object keyAsObject = reader.GetString()!; - key = (TKey)keyAsObject; - unescapedPropertyNameAsString = (string)keyAsObject; + unescapedPropertyNameAsString = reader.GetString()!; + key = (TKey)(object)unescapedPropertyNameAsString; } else { @@ -111,7 +104,7 @@ internal sealed override bool OnTryRead( // Read the value and add. reader.ReadWithVerify(); TValue element = elementConverter.Read(ref reader, typeof(TValue), options); - Add(in key, in element!, options, ref state); + Add(key, element!, options, ref state); } } else @@ -130,15 +123,12 @@ internal sealed override bool OnTryRead( // Read method would have thrown if otherwise. Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); - // Copy key name for JSON Path support in case of error. - string unescapedPropertyNameAsString; - TKey key; + string unescapedPropertyNameAsString; if (IsStringKey) { - object keyAsObject = reader.GetString()!; - key = (TKey)keyAsObject; - unescapedPropertyNameAsString = (string)keyAsObject; + unescapedPropertyNameAsString = reader.GetString()!; + key = (TKey)(object)unescapedPropertyNameAsString; } else { @@ -147,12 +137,13 @@ internal sealed override bool OnTryRead( unescapedPropertyNameAsString = reader.GetString()!; } + // Copy key name for JSON Path support in case of error. state.Current.JsonPropertyNameAsString = unescapedPropertyNameAsString; reader.ReadWithVerify(); // Get the value from the converter and add it. elementConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValue element); - Add(in key, in element!, options, ref state); + Add(key, element!, options, ref state); } } } @@ -209,7 +200,7 @@ internal sealed override bool OnTryRead( } // Process all elements. - JsonConverter elementConverter = GetElementConverter(ref state); + JsonConverter elementConverter = GetValueConverter(state.Current.JsonClassInfo); while (true) { if (state.Current.PropertyState == StackFramePropertyState.None) @@ -246,15 +237,12 @@ internal sealed override bool OnTryRead( } } - // Copy key name for JSON Path support in case of error. - string unescapedPropertyNameAsString; - TKey key; + string unescapedPropertyNameAsString; if (IsStringKey) { - object keyAsObject = reader.GetString()!; - key = (TKey)keyAsObject; - unescapedPropertyNameAsString = (string)keyAsObject; + unescapedPropertyNameAsString = reader.GetString()!; + key = (TKey)(object)unescapedPropertyNameAsString; } else { @@ -263,6 +251,7 @@ internal sealed override bool OnTryRead( unescapedPropertyNameAsString = reader.GetString()!; } + // Copy key name for JSON Path support in case of error. state.Current.JsonPropertyNameAsString = unescapedPropertyNameAsString; state.Current.DictionaryKey = key; } @@ -289,7 +278,7 @@ internal sealed override bool OnTryRead( } TKey key = (TKey)state.Current.DictionaryKey!; - Add(in key, in element!, options, ref state); + Add(key, element!, options, ref state); state.Current.EndElement(); } } 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 f14fb2e0b8e4c..d06ad4aae37ea 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 @@ -15,7 +15,7 @@ internal sealed class DictionaryOfTKeyTValueConverter where TCollection : Dictionary where TKey : notnull { - protected override void Add(in TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) { ((TCollection)state.Current.ReturnValue!)[key] = value; } @@ -51,7 +51,7 @@ protected internal override bool OnWriteResume( } JsonConverter keyConverter = GetKeyConverter(options); - JsonConverter converter = GetValueConverter(ref state); + JsonConverter converter = GetValueConverter(state.Current.JsonClassInfo); if (!state.SupportContinuation && converter.CanUseDirectReadOrWrite) { // Fast path that avoids validation and extra indirection. 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 59b0792a828e8..aef50278a8097 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 @@ -16,11 +16,16 @@ internal sealed class IDictionaryConverter : DictionaryDefaultConverter where TCollection : IDictionary { - protected override void Add(in string key, in object? value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(string key, in object? value, JsonSerializerOptions options, ref ReadStack state) { ((IDictionary)state.Current.ReturnValue!)[key] = value; } + private JsonConverter? _objectConverter; + + private JsonConverter GetObjectKeyConverter(JsonSerializerOptions options) + => _objectConverter ??= (JsonConverter)options.GetDictionaryKeyConverter(typeof(object)); + protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStack state) { JsonClassInfo classInfo = state.Current.JsonClassInfo; @@ -68,7 +73,7 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio enumerator = (IDictionaryEnumerator)state.Current.CollectionEnumerator; } - JsonConverter converter = GetValueConverter(ref state); + JsonConverter converter = GetValueConverter(state.Current.JsonClassInfo); do { if (ShouldFlush(writer, ref state)) @@ -89,12 +94,9 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio } else { - // IDictionary is a spacial case since it has polymorphic object semantics on serialization + // IDictionary is a special case since it has polymorphic object semantics on serialization // but needs to use JsonConverter on deserialization. - JsonConverter? keyConverter = options.GetDictionaryKeyConverter(typeof(object)); - Debug.Assert(keyConverter != null); - - JsonConverter objectKeyConverter = (JsonConverter)keyConverter; + JsonConverter objectKeyConverter = GetObjectKeyConverter(options); objectKeyConverter.WriteWithQuotes(writer, key, options, ref state); } } 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 27971fb950be1..3fb7de97f8ebc 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 @@ -15,7 +15,7 @@ internal sealed class IDictionaryOfTKeyTValueConverter where TKey : notnull { - protected override void Add(in TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) { ((TCollection)state.Current.ReturnValue!)[key] = value; } @@ -72,7 +72,7 @@ protected internal override bool OnWriteResume( } JsonConverter keyConverter = GetKeyConverter(options); - JsonConverter converter = GetValueConverter(ref state); + JsonConverter converter = GetValueConverter(state.Current.JsonClassInfo); do { if (ShouldFlush(writer, ref state)) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs index ee0536c612c65..7b1cdd130f50d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs @@ -68,7 +68,7 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer converterType = typeof(ListOfTConverter<,>); elementType = actualTypeToConvert.GetGenericArguments()[0]; } - // Dictionary or deriving from Dictionary + // Dictionary or deriving from Dictionary else if ((actualTypeToConvert = typeToConvert.GetCompatibleGenericBaseClass(typeof(Dictionary<,>))) != null) { genericArgs = actualTypeToConvert.GetGenericArguments(); @@ -84,7 +84,7 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer dictionaryKeyType = genericArgs[0]; elementType = genericArgs[1]; } - // IDictionary or deriving from IDictionary + // IDictionary or deriving from IDictionary else if ((actualTypeToConvert = typeToConvert.GetCompatibleGenericInterface(typeof(IDictionary<,>))) != null) { genericArgs = actualTypeToConvert.GetGenericArguments(); @@ -92,7 +92,7 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer dictionaryKeyType = genericArgs[0]; elementType = genericArgs[1]; } - // IReadOnlyDictionary or deriving from IReadOnlyDictionary + // IReadOnlyDictionary or deriving from IReadOnlyDictionary else if ((actualTypeToConvert = typeToConvert.GetCompatibleGenericInterface(typeof(IReadOnlyDictionary<,>))) != null) { genericArgs = actualTypeToConvert.GetGenericArguments(); 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 598e8aa671deb..72c37ffde7eab 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 @@ -11,7 +11,7 @@ internal sealed class IReadOnlyDictionaryOfTKeyTValueConverter where TKey : notnull { - protected override void Add(in TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) { ((Dictionary)state.Current.ReturnValue!)[key] = value; } @@ -43,7 +43,7 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio } JsonConverter keyConverter = GetKeyConverter(options); - JsonConverter converter = GetValueConverter(ref state); + JsonConverter converter = GetValueConverter(state.Current.JsonClassInfo); do { if (ShouldFlush(writer, ref 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 e86b74536ac53..6f75711cc98ef 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 @@ -11,7 +11,7 @@ internal sealed class ImmutableDictionaryOfTKeyTValueConverter where TKey : notnull { - protected override void Add(in TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) + protected override void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state) { ((Dictionary)state.Current.ReturnValue!)[key] = value; } @@ -54,7 +54,7 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio } JsonConverter keyConverter = GetKeyConverter(options); - JsonConverter converter = GetValueConverter(ref state); + JsonConverter converter = GetValueConverter(state.Current.JsonClassInfo); do { if (ShouldFlush(writer, ref state)) 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 b9a6f960fc8e9..1280d2abf0fc5 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 @@ -18,12 +18,7 @@ public override void Write(Utf8JsonWriter writer, byte value, JsonSerializerOpti internal override byte ReadWithQuotes(ref Utf8JsonReader reader) { - if (!reader.TryGetByteCore(out byte value)) - { - throw ThrowHelper.GetFormatException(NumericType.Byte); - } - - return value; + return reader.GetByteWithQuotes(); } internal override void WriteWithQuotes(Utf8JsonWriter writer, byte value, JsonSerializerOptions options, ref WriteStack state) 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 bfc9a72c4062d..41901e39560c3 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 @@ -30,14 +30,7 @@ public override void Write(Utf8JsonWriter writer, char value, JsonSerializerOpti } internal override char ReadWithQuotes(ref Utf8JsonReader reader) - { - string? str = reader.GetString(); - if (string.IsNullOrEmpty(str) || str.Length > 1) - { - throw ThrowHelper.GetInvalidOperationException_ExpectedChar(JsonTokenType.String); - } - return str[0]; - } + => Read(ref reader, default!, default!); internal override void WriteWithQuotes(Utf8JsonWriter writer, char value, JsonSerializerOptions options, ref WriteStack state) { 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 9a63650785579..00754b5b8ed82 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 @@ -18,12 +18,7 @@ public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializer internal override DateTime ReadWithQuotes(ref Utf8JsonReader reader) { - if (!reader.TryGetDateTimeCore(out DateTime value)) - { - throw ThrowHelper.GetFormatException(DataType.DateTime); - } - - return value; + return reader.GetDateTimeNoValidation(); } internal override void WriteWithQuotes(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options, ref WriteStack state) 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 528022c09f6eb..e6eada9de0462 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 @@ -18,12 +18,7 @@ public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSeri internal override DateTimeOffset ReadWithQuotes(ref Utf8JsonReader reader) { - if (!reader.TryGetDateTimeOffsetCore(out DateTimeOffset value)) - { - throw ThrowHelper.GetFormatException(DataType.DateTimeOffset); - } - - return value; + return reader.GetDateTimeOffsetNoValidation(); } internal override void WriteWithQuotes(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options, ref WriteStack state) 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 34e8a41dbf7aa..0f2350fa34175 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 @@ -18,12 +18,7 @@ public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerO internal override decimal ReadWithQuotes(ref Utf8JsonReader reader) { - if (!reader.TryGetDecimalCore(out decimal value)) - { - throw ThrowHelper.GetFormatException(NumericType.Decimal); - } - - return value; + return reader.GetDecimalWithQuotes(); } internal override void WriteWithQuotes(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options, ref WriteStack state) 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 401ec768d3829..5814f89c293a6 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 @@ -18,12 +18,7 @@ public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOp internal override double ReadWithQuotes(ref Utf8JsonReader reader) { - if (!reader.TryGetDoubleCore(out double value)) - { - throw ThrowHelper.GetFormatException(NumericType.Double); - } - - return value; + return reader.GetDoubleWithQuotes(); } internal override void WriteWithQuotes(Utf8JsonWriter writer, double value, JsonSerializerOptions options, ref WriteStack state) 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 77eb78dc3f355..c4b4dd75468f8 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 @@ -83,15 +83,7 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial return default; } - // Try parsing case sensitive first - string? enumString = reader.GetString(); - if (!Enum.TryParse(enumString, out T value) - && !Enum.TryParse(enumString, ignoreCase: true, out value)) - { - ThrowHelper.ThrowJsonException(); - return default; - } - return value; + return ReadWithQuotes(ref reader); } if (token != JsonTokenType.Number || !_converterOptions.HasFlag(EnumConverterOptions.AllowNumbers)) @@ -321,8 +313,8 @@ private string FormatEnumValueToString(string value, JavaScriptEncoder? encoder) internal override T ReadWithQuotes(ref Utf8JsonReader reader) { string? enumString = reader.GetString(); - Debug.Assert(enumString != null); + // Try parsing case sensitive first if (!Enum.TryParse(enumString, out T value) && !Enum.TryParse(enumString, ignoreCase: true, out value)) { 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 139f70488b14a..6328144fc6bf7 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 @@ -1,34 +1,29 @@ -// 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. - -namespace System.Text.Json.Serialization.Converters -{ - internal sealed class GuidConverter : JsonConverter - { - public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return reader.GetGuid(); - } - - public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options) - { - writer.WriteStringValue(value); - } - - internal override Guid ReadWithQuotes(ref Utf8JsonReader reader) - { - if (!reader.TryGetGuidCore(out Guid value)) - { - throw ThrowHelper.GetFormatException(DataType.Guid); - } - - return value; - } - - internal override void WriteWithQuotes(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options, ref WriteStack state) - { - writer.WritePropertyName(value); - } - } -} +// 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. + +namespace System.Text.Json.Serialization.Converters +{ + internal sealed class GuidConverter : JsonConverter + { + public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetGuid(); + } + + public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options) + { + writer.WriteStringValue(value); + } + + internal override Guid ReadWithQuotes(ref Utf8JsonReader reader) + { + return reader.GetGuidNoValidation(); + } + + internal override void WriteWithQuotes(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 3f8d5782735ab..8104a987625b2 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 @@ -20,15 +20,10 @@ public override void Write(Utf8JsonWriter writer, short value, JsonSerializerOpt internal override short ReadWithQuotes(ref Utf8JsonReader reader) { - if (!reader.TryGetInt16Core(out short value)) - { - throw ThrowHelper.GetFormatException(NumericType.Int16); - } - - return value; + return reader.GetInt16WithQuotes(); } - internal override void WriteWithQuotes(Utf8JsonWriter writer, [DisallowNull] short value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteWithQuotes(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 0de33c225f4d0..f6f6712c4dff7 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 @@ -18,12 +18,7 @@ public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptio internal override int ReadWithQuotes(ref Utf8JsonReader reader) { - if (!reader.TryGetInt32Core(out int value)) - { - throw ThrowHelper.GetFormatException(NumericType.Int32); - } - - return value; + return reader.GetInt32WithQuotes(); } internal override void WriteWithQuotes(Utf8JsonWriter writer, int value, JsonSerializerOptions options, ref WriteStack state) 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 8384e0c96f9c4..a8817af4c924c 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 @@ -18,12 +18,7 @@ public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOpti internal override long ReadWithQuotes(ref Utf8JsonReader reader) { - if (!reader.TryGetInt64Core(out long value)) - { - throw ThrowHelper.GetFormatException(NumericType.Int64); - } - - return value; + return reader.GetInt64WithQuotes(); } internal override void WriteWithQuotes(Utf8JsonWriter writer, long value, JsonSerializerOptions options, ref WriteStack state) 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 af8e777565f6f..f4a2339e4831e 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 @@ -18,12 +18,7 @@ public override void Write(Utf8JsonWriter writer, sbyte value, JsonSerializerOpt internal override sbyte ReadWithQuotes(ref Utf8JsonReader reader) { - if (!reader.TryGetSByteCore(out sbyte value)) - { - throw ThrowHelper.GetFormatException(NumericType.SByte); - } - - return value; + return reader.GetSByteWithQuotes(); } internal override void WriteWithQuotes(Utf8JsonWriter writer, sbyte value, JsonSerializerOptions options, ref WriteStack state) 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 57d62732b8169..1653a65e7e481 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 @@ -18,12 +18,7 @@ public override void Write(Utf8JsonWriter writer, float value, JsonSerializerOpt internal override float ReadWithQuotes(ref Utf8JsonReader reader) { - if (!reader.TryGetSingleCore(out float value)) - { - throw ThrowHelper.GetFormatException(NumericType.Single); - } - - return value; + return reader.GetSingleWithQuotes(); } internal override void WriteWithQuotes(Utf8JsonWriter writer, float value, JsonSerializerOptions options, ref WriteStack state) 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 337128016c037..d0d958ab7a44f 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 @@ -6,7 +6,7 @@ namespace System.Text.Json.Serialization.Converters { - internal sealed class StringConverter : JsonConverter + internal sealed class StringConverter : JsonConverter { public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { @@ -23,7 +23,7 @@ internal override string ReadWithQuotes(ref Utf8JsonReader reader) return reader.GetString()!; } - internal override void WriteWithQuotes(Utf8JsonWriter writer, [DisallowNull] string? value, JsonSerializerOptions options, ref WriteStack state) + internal override void WriteWithQuotes(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 dd6d49118443d..44f20a21e5d9c 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 @@ -18,12 +18,7 @@ public override void Write(Utf8JsonWriter writer, ushort value, JsonSerializerOp internal override ushort ReadWithQuotes(ref Utf8JsonReader reader) { - if (!reader.TryGetUInt16Core(out ushort value)) - { - throw ThrowHelper.GetFormatException(NumericType.UInt16); - } - - return value; + return reader.GetUInt16WithQuotes(); } internal override void WriteWithQuotes(Utf8JsonWriter writer, ushort value, JsonSerializerOptions options, ref WriteStack state) 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 5e49916aa0a88..cea8390da69fc 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 @@ -18,12 +18,7 @@ public override void Write(Utf8JsonWriter writer, uint value, JsonSerializerOpti internal override uint ReadWithQuotes(ref Utf8JsonReader reader) { - if (!reader.TryGetUInt32Core(out uint value)) - { - throw ThrowHelper.GetFormatException(NumericType.UInt16); - } - - return value; + return reader.GetUInt32WithQuotes(); } internal override void WriteWithQuotes(Utf8JsonWriter writer, uint value, JsonSerializerOptions options, ref WriteStack state) 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 5233f908484c4..3f07e3623a22f 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 @@ -18,12 +18,7 @@ public override void Write(Utf8JsonWriter writer, ulong value, JsonSerializerOpt internal override ulong ReadWithQuotes(ref Utf8JsonReader reader) { - if (!reader.TryGetUInt64Core(out ulong value)) - { - throw ThrowHelper.GetFormatException(NumericType.UInt64); - } - - return value; + return reader.GetUInt64WithQuotes(); } internal override void WriteWithQuotes(Utf8JsonWriter writer, ulong value, JsonSerializerOptions options, ref WriteStack state) 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 17fa35c3c8950..c8db4408709f0 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 @@ -75,11 +75,16 @@ void Add(JsonConverter converter) => internal JsonConverter GetDictionaryKeyConverter(Type keyType) { + if (_dictionaryKeyConverters == null) + { + _dictionaryKeyConverters = GetDictionaryKeyConverters(); + } + if (!_dictionaryKeyConverters.TryGetValue(keyType, out JsonConverter? converter)){ if (keyType.IsEnum) { converter = GetEnumConverter(); - _dictionaryKeyConverters.Add(keyType, converter); + _dictionaryKeyConverters[keyType] = converter; } else { @@ -98,12 +103,12 @@ JsonConverter GetEnumConverter() culture: null)!; } - private readonly Dictionary _dictionaryKeyConverters = GetDictionaryKeyConverters(); + private ConcurrentDictionary? _dictionaryKeyConverters; - private static Dictionary GetDictionaryKeyConverters() + private static ConcurrentDictionary GetDictionaryKeyConverters() { const int NumberOfConverters = 18; - var converters = new Dictionary(NumberOfConverters); + var converters = new ConcurrentDictionary(Environment.ProcessorCount, NumberOfConverters); // When adding to this, update NumberOfConverters above. Add(new BooleanConverter()); @@ -130,7 +135,7 @@ private static Dictionary GetDictionaryKeyConverters() return converters; void Add(JsonConverter converter) => - converters.Add(converter.TypeToConvert, converter); + converters[converter.TypeToConvert] = converter; } /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs index 3f5aaf6e60e5d..0ed995b641d7b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs @@ -380,7 +380,7 @@ internal void WritePropertyName(DateTime value) { Span buffer = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; JsonWriterHelper.WriteDateTimeTrimmed(buffer, value, out int bytesWritten); - WritePropertyName(buffer.Slice(0, bytesWritten)); + WritePropertyNameUnescaped(buffer.Slice(0, bytesWritten)); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs index b4d8549459dfa..62ba78f9e0df5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs @@ -379,7 +379,7 @@ internal void WritePropertyName(DateTimeOffset value) { Span buffer = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; JsonWriterHelper.WriteDateTimeOffsetTrimmed(buffer, value, out int bytesWritten); - WritePropertyName(buffer.Slice(0, bytesWritten)); + WritePropertyNameUnescaped(buffer.Slice(0, bytesWritten)); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs index 7d0facd51e782..626098736f94e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs @@ -368,7 +368,7 @@ internal void WritePropertyName(decimal value) Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatDecimalLength]; bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten); Debug.Assert(result); - WritePropertyName(utf8PropertyName.Slice(0, bytesWritten)); + WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten)); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs index eab8b38d512d9..63047537ab372 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs @@ -373,7 +373,7 @@ internal void WritePropertyName(double value) Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatDoubleLength]; bool result = TryFormatDouble(value, utf8PropertyName, out int bytesWritten); Debug.Assert(result); - WritePropertyName(utf8PropertyName.Slice(0, bytesWritten)); + WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten)); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs index 27a1a2f93669b..2af8936863bc5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs @@ -372,7 +372,7 @@ internal void WritePropertyName(float value) Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatSingleLength]; bool result = TryFormatSingle(value, utf8PropertyName, out int bytesWritten); Debug.Assert(result); - WritePropertyName(utf8PropertyName.Slice(0, bytesWritten)); + WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten)); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs index 7983dd31d47ad..062e1ea758e6e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs @@ -384,7 +384,7 @@ internal void WritePropertyName(Guid value) Span utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatGuidLength]; bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten); Debug.Assert(result); - WritePropertyName(utf8PropertyName.Slice(0, bytesWritten)); + WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten)); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs index 87b3dde623f8e..625d7711e0732 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs @@ -512,7 +512,7 @@ internal void WritePropertyName(bool value) bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten); Debug.Assert(result); - WritePropertyName(utf8PropertyName.Slice(0, bytesWritten)); + WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten)); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs index 8768b9ce48f8e..f24aad62ac1a7 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs @@ -443,7 +443,7 @@ internal void WritePropertyName(long value) bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten); Debug.Assert(result); - WritePropertyName(utf8PropertyName.Slice(0, bytesWritten)); + WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten)); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs index ddcca39fa28f4..d0c19f67a5180 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs @@ -255,6 +255,15 @@ public void WritePropertyName(ReadOnlySpan utf8PropertyName) _tokenType = JsonTokenType.PropertyName; } + private void WritePropertyNameUnescaped(ReadOnlySpan utf8PropertyName) + { + JsonWriterHelper.ValidateProperty(utf8PropertyName); + WriteStringByOptionsPropertyName(utf8PropertyName); + + _currentDepth &= JsonConstants.RemoveFlagsBitMask; + _tokenType = JsonTokenType.PropertyName; + } + private void WriteStringEscapeProperty(ReadOnlySpan utf8PropertyName, int firstEscapeIndexProp) { Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs index 72ce8408bdff4..0430768b43be6 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs @@ -452,7 +452,7 @@ internal void WritePropertyName(ulong value) bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten); Debug.Assert(result); - WritePropertyName(utf8PropertyName.Slice(0, bytesWritten)); + WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten)); } } } 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 d684f82b25e5e..a0bd1ddd23d46 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 @@ -84,17 +84,16 @@ public class DictionaryCharKey : DictionaryKeyTestsBase public class DictionaryDateTimeKey : DictionaryKeyTestsBase { - protected override string _expectedJson => $@"{{""{DateTime.MaxValue.ToString("O")}"":1}}"; + protected override string _expectedJson => $@"{{""{DateTime.MaxValue:O}"":1}}"; protected override DateTime Key => DateTime.MaxValue; protected override int Value => 1; } - public class DictionaryDateTimeOffsetKey : DictionaryKeyTestsBase + public class DictionaryDateTimeOffsetKey : DictionaryKeyTestsBase { - //TODO: The plus sign is escaped for the key but not for the value. Is this correct? - protected override string _expectedJson => $@"{{""9999-12-31T23:59:59.9999999\u002B00:00"":""{DateTimeOffset.MaxValue.ToString("O")}""}}"; + protected override string _expectedJson => $@"{{""{DateTimeOffset.MaxValue:O}"":1}}"; protected override DateTimeOffset Key => DateTimeOffset.MaxValue; - protected override DateTimeOffset Value => DateTimeOffset.MaxValue; + protected override int Value => 1; } public class DictionaryDecimalKey : DictionaryKeyTestsBase @@ -103,17 +102,9 @@ public class DictionaryDecimalKey : DictionaryKeyTestsBase protected override int Value => 1; } - //public class DictionaryDoubleKey : DictionaryKeyTestsBase - //{ - // //TODO: The plus sign is escaped for the key but not for the value. Is this correct? - // protected override string _expectedJson => $@"{{""1.7976931348623157E\u002B308"":{double.MaxValue}}}"; - // protected override double Key => double.MaxValue; - // protected override double Value => double.MaxValue; - //} - public class DictionaryDoubleKey : DictionaryKeyTestsBase { - protected override double Key => 1; + protected override double Key => double.MaxValue; protected override int Value => 1; } @@ -160,16 +151,9 @@ public class DictionarySByteKey : DictionaryKeyTestsBase protected override int Value => 1; } - //public class DictionarySingleKey : DictionaryKeyTestsBase - //{ - // //TODO: The plus sign is escaped for the key but not for the value. Is this correct? - // protected override float Key => float.MaxValue; - // protected override float Value => float.MaxValue; - //} - public class DictionarySingleKey : DictionaryKeyTestsBase { - protected override float Key => 1; + protected override float Key => float.MaxValue; protected override int Value => 1; } @@ -538,13 +522,81 @@ public void TestEnumKeyWithNotValidIdentifier() myEnumFlagsIntDictionary = JsonSerializer.Deserialize>(json); Assert.Equal(1, myEnumFlagsIntDictionary[(MyEnumFlags)(-1)]); } + + [Theory] + [MemberData(nameof(DictionaryKeysWithSpecialCharacters))] + public void EnsureNonStringKeysDontGetEscapedOnSerialize(object key, string expectedKeySerialized) + { + Dictionary root = new Dictionary(); + root.Add(key, 1); + + string json = JsonSerializer.Serialize(root); + Assert.Contains(expectedKeySerialized, json); + } + + public static IEnumerable DictionaryKeysWithSpecialCharacters => + new List + { + new object[] { float.MaxValue, "3.4028235E+38" }, + new object[] { double.MaxValue, "1.7976931348623157E+308" }, + new object[] { DateTimeOffset.MaxValue, "9999-12-31T23:59:59.9999999+00:00" } + }; + + [Theory] + [MemberData(nameof(EscapedMemberData))] + public void TestEscapedValuesOnDeserialize(string escapedPropertyName, object expectedDictionaryKey, Type dictionaryType) + { + string json = $@"{{""{escapedPropertyName}"":1}}"; + IDictionary root = (IDictionary)JsonSerializer.Deserialize(json, dictionaryType); + + bool containsKey = root.Contains(expectedDictionaryKey); + Assert.True(containsKey); + Assert.Equal(1, root[expectedDictionaryKey]); + } + + public static IEnumerable EscapedMemberData => + new List + { + new object[] { @"\u0031\u0032\u0037", + sbyte.MaxValue, typeof(Dictionary) }, + new object[] { @"\u0032\u0035\u0035", + byte.MaxValue, typeof(Dictionary) }, + new object[] { @"\u0033\u0032\u0037\u0036\u0037", + short.MaxValue, typeof(Dictionary) }, + new object[] { @"\u0036\u0035\u0035\u0033\u0035", + ushort.MaxValue, typeof(Dictionary) }, + new object[] { @"\u0032\u0031\u0034\u0037\u0034\u0038\u0033\u0036\u0034\u0037", + int.MaxValue, typeof(Dictionary) }, + new object[] { @"\u0034\u0032\u0039\u0034\u0039\u0036\u0037\u0032\u0039\u0035", + uint.MaxValue, typeof(Dictionary) }, + new object[] { @"\u0039\u0032\u0032\u0033\u0033\u0037\u0032\u0030\u0033\u0036\u0038\u0035\u0034\u0037\u0037\u0035\u0038\u0030\u0037", + long.MaxValue, typeof(Dictionary) }, + new object[] { @"\u0031\u0038\u0034\u0034\u0036\u0037\u0034\u0034\u0030\u0037\u0033\u0037\u0030\u0039\u0035\u0035\u0031\u0036\u0031\u0035", + ulong.MaxValue, typeof(Dictionary) }, + new object[] { @"\u0033\u002e\u0034\u0030\u0032\u0038\u0032\u0033\u0034\u0037\u0045\u002b\u0033\u0038", + float.MaxValue, typeof(Dictionary) }, + new object[] { @"\u0031\u002e\u0037\u0039\u0037\u0036\u0039\u0033\u0031\u0033\u0034\u0038\u0036\u0032\u0033\u0031\u0035\u0037\u0045\u002b\u0033\u0030\u0038", + double.MaxValue, typeof(Dictionary) }, + new object[] { @"\u0037\u0039\u0032\u0032\u0038\u0031\u0036\u0032\u0035\u0031\u0034\u0032\u0036\u0034\u0033\u0033\u0037\u0035\u0039\u0033\u0035\u0034\u0033\u0039\u0035\u0030\u0033\u0033\u0035", + decimal.MaxValue, typeof(Dictionary) }, + new object[] { @"\u0039\u0039\u0039\u0039\u002d\u0031\u0032\u002d\u0033\u0031\u0054\u0032\u0033\u003a\u0035\u0039\u003a\u0035\u0039\u002e\u0039\u0039\u0039\u0039\u0039\u0039\u0039", + DateTime.MaxValue, typeof(Dictionary) }, + new object[] { @"\u0039\u0039\u0039\u0039\u002d\u0031\u0032\u002d\u0033\u0031\u0054\u0032\u0033\u003a\u0035\u0039\u003a\u0035\u0039\u002e\u0039\u0039\u0039\u0039\u0039\u0039\u0039\u002b\u0030\u0030\u003a\u0030\u0030", + DateTimeOffset.MaxValue, typeof(Dictionary) }, + new object[] { @"\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u002d\u0030\u0030\u0030\u0030\u002d\u0030\u0030\u0030\u0030\u002d\u0030\u0030\u0030\u0030\u002d\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u0030", + Guid.Empty, typeof(Dictionary) }, + new object[] { @"\u0042\u0061\u0072", + MyEnum.Bar, typeof(Dictionary) }, + new object[] { @"\u0042\u0061\u0072\u002c\u0042\u0061\u007a", + MyEnumFlags.Bar | MyEnumFlags.Baz, typeof(Dictionary) }, + new object[] { @"\u002b", '+', typeof(Dictionary) } + }; } public class MyPublicClass { } public struct MyPublicStruct { } - public enum MyEnum { Foo, From 2d56bfba9f9844203449ddd2467ecc48c7c81e9a Mon Sep 17 00:00:00 2001 From: David Cantu Date: Tue, 30 Jun 2020 16:02:59 -0700 Subject: [PATCH 10/16] Remove TryGet*Core methods to mitigate perf regression --- .../Text/Json/Reader/Utf8JsonReader.TryGet.cs | 166 +++++++----------- 1 file changed, 66 insertions(+), 100 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs index 05bdb807b689e..a37d03c6057ac 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs @@ -142,11 +142,14 @@ public byte GetByte() internal byte GetByteWithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - if (!TryGetByteCore(out byte value, span)) + + if (Utf8Parser.TryParse(span, out byte tmp, out int bytesConsumed) + && span.Length == bytesConsumed) { - throw ThrowHelper.GetFormatException(NumericType.Byte); + return tmp; } - return value; + + throw ThrowHelper.GetFormatException(NumericType.Byte); } /// @@ -177,11 +180,14 @@ public sbyte GetSByte() internal sbyte GetSByteWithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - if (!TryGetSByteCore(out sbyte value, span)) + + if (Utf8Parser.TryParse(span, out sbyte tmp, out int bytesConsumed) + && span.Length == bytesConsumed) { - throw ThrowHelper.GetFormatException(NumericType.SByte); + return tmp; } - return value; + + throw ThrowHelper.GetFormatException(NumericType.SByte); } /// @@ -211,11 +217,14 @@ public short GetInt16() internal short GetInt16WithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - if (!TryGetInt16Core(out short value, span)) + + if (Utf8Parser.TryParse(span, out short tmp, out int bytesConsumed) + && span.Length == bytesConsumed) { - throw ThrowHelper.GetFormatException(NumericType.Int16); + return tmp; } - return value; + + throw ThrowHelper.GetFormatException(NumericType.Int16); } /// @@ -245,11 +254,14 @@ public int GetInt32() internal int GetInt32WithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - if (!TryGetInt32Core(out int value, span)) + + if (Utf8Parser.TryParse(span, out int tmp, out int bytesConsumed) + && span.Length == bytesConsumed) { - throw ThrowHelper.GetFormatException(NumericType.Int32); + return tmp; } - return value; + + throw ThrowHelper.GetFormatException(NumericType.Int32); } /// @@ -279,11 +291,14 @@ public long GetInt64() internal long GetInt64WithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - if (!TryGetInt64Core(out long value, span)) + + if (Utf8Parser.TryParse(span, out long tmp, out int bytesConsumed) + && span.Length == bytesConsumed) { - throw ThrowHelper.GetFormatException(NumericType.Int64); + return tmp; } - return value; + + throw ThrowHelper.GetFormatException(NumericType.Int64); } /// @@ -314,11 +329,14 @@ public ushort GetUInt16() internal ushort GetUInt16WithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - if (!TryGetUInt16Core(out ushort value, span)) + + if (Utf8Parser.TryParse(span, out ushort tmp, out int bytesConsumed) + && span.Length == bytesConsumed) { - throw ThrowHelper.GetFormatException(NumericType.UInt16); + return tmp; } - return value; + + throw ThrowHelper.GetFormatException(NumericType.UInt16); } /// @@ -349,11 +367,14 @@ public uint GetUInt32() internal uint GetUInt32WithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - if (!TryGetUInt32Core(out uint value, span)) + + if (Utf8Parser.TryParse(span, out uint tmp, out int bytesConsumed) + && span.Length == bytesConsumed) { - throw ThrowHelper.GetFormatException(NumericType.UInt32); + return tmp; } - return value; + + throw ThrowHelper.GetFormatException(NumericType.UInt32); } /// @@ -384,11 +405,14 @@ public ulong GetUInt64() internal ulong GetUInt64WithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - if (!TryGetUInt64Core(out ulong value, span)) + + if (Utf8Parser.TryParse(span, out ulong tmp, out int bytesConsumed) + && span.Length == bytesConsumed) { - throw ThrowHelper.GetFormatException(NumericType.UInt64); + return tmp; } - return value; + + throw ThrowHelper.GetFormatException(NumericType.UInt64); } /// @@ -417,11 +441,14 @@ public float GetSingle() internal float GetSingleWithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - if (!TryGetSingleCore(out float value, span)) + + if (Utf8Parser.TryParse(span, out float tmp, out int bytesConsumed, _numberFormat) + && span.Length == bytesConsumed) { - throw ThrowHelper.GetFormatException(NumericType.Single); + return tmp; } - return value; + + throw ThrowHelper.GetFormatException(NumericType.Single); } /// @@ -450,11 +477,14 @@ public double GetDouble() internal double GetDoubleWithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - if (!TryGetDoubleCore(out double value, span)) + + if (Utf8Parser.TryParse(span, out double tmp, out int bytesConsumed, _numberFormat) + && span.Length == bytesConsumed) { - throw ThrowHelper.GetFormatException(NumericType.Double); + return tmp; } - return value; + + throw ThrowHelper.GetFormatException(NumericType.Double); } /// @@ -483,11 +513,14 @@ public decimal GetDecimal() internal decimal GetDecimalWithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - if (!TryGetDecimalCore(out decimal value, span)) + + if (Utf8Parser.TryParse(span, out decimal tmp, out int bytesConsumed, _numberFormat) + && span.Length == bytesConsumed) { - throw ThrowHelper.GetFormatException(NumericType.Decimal); + return tmp; } - return value; + + throw ThrowHelper.GetFormatException(NumericType.Decimal); } /// @@ -637,12 +670,6 @@ public bool TryGetByte(out byte value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; - return TryGetByteCore(out value, span); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool TryGetByteCore(out byte value, ReadOnlySpan span) - { if (Utf8Parser.TryParse(span, out byte tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -673,12 +700,6 @@ public bool TryGetSByte(out sbyte value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; - return TryGetSByteCore(out value, span); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool TryGetSByteCore(out sbyte value, ReadOnlySpan span) - { if (Utf8Parser.TryParse(span, out sbyte tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -708,12 +729,6 @@ public bool TryGetInt16(out short value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; - return TryGetInt16Core(out value, span); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool TryGetInt16Core(out short value, ReadOnlySpan span) - { if (Utf8Parser.TryParse(span, out short tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -743,12 +758,6 @@ public bool TryGetInt32(out int value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; - return TryGetInt32Core(out value, span); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool TryGetInt32Core(out int value, ReadOnlySpan span) - { if (Utf8Parser.TryParse(span, out int tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -778,12 +787,6 @@ public bool TryGetInt64(out long value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; - return TryGetInt64Core(out value, span); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool TryGetInt64Core(out long value, ReadOnlySpan span) - { if (Utf8Parser.TryParse(span, out long tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -814,12 +817,6 @@ public bool TryGetUInt16(out ushort value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; - return TryGetUInt16Core(out value, span); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool TryGetUInt16Core(out ushort value, ReadOnlySpan span) - { if (Utf8Parser.TryParse(span, out ushort tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -850,12 +847,6 @@ public bool TryGetUInt32(out uint value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; - return TryGetUInt32Core(out value, span); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool TryGetUInt32Core(out uint value, ReadOnlySpan span) - { if (Utf8Parser.TryParse(span, out uint tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -886,12 +877,6 @@ public bool TryGetUInt64(out ulong value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; - return TryGetUInt64Core(out value, span); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool TryGetUInt64Core(out ulong value, ReadOnlySpan span) - { if (Utf8Parser.TryParse(span, out ulong tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -921,12 +906,6 @@ public bool TryGetSingle(out float value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; - return TryGetSingleCore(out value, span); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool TryGetSingleCore(out float value, ReadOnlySpan span) - { if (Utf8Parser.TryParse(span, out float tmp, out int bytesConsumed, _numberFormat) && span.Length == bytesConsumed) { @@ -956,12 +935,6 @@ public bool TryGetDouble(out double value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; - return TryGetDoubleCore(out value, span); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool TryGetDoubleCore(out double value, ReadOnlySpan span) - { if (Utf8Parser.TryParse(span, out double tmp, out int bytesConsumed, _numberFormat) && span.Length == bytesConsumed) { @@ -991,12 +964,6 @@ public bool TryGetDecimal(out decimal value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; - return TryGetDecimalCore(out value, span); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool TryGetDecimalCore(out decimal value, ReadOnlySpan span) - { if (Utf8Parser.TryParse(span, out decimal tmp, out int bytesConsumed, _numberFormat) && span.Length == bytesConsumed) { @@ -1030,7 +997,6 @@ public bool TryGetDateTime(out DateTime value) internal bool TryGetDateTimeCore(out DateTime value) { - ReadOnlySpan span = stackalloc byte[0]; if (HasValueSequence) From 637c68fb62db34e93e3601b6e2a601edb3c7e706 Mon Sep 17 00:00:00 2001 From: David Cantu Date: Tue, 30 Jun 2020 16:11:13 -0700 Subject: [PATCH 11/16] Remove duplicated escaping tests --- ...CollectionTests.Dictionary.NonStringKey.cs | 98 +++---------------- 1 file changed, 13 insertions(+), 85 deletions(-) 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 a0bd1ddd23d46..24ab65fe26e1e 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 @@ -327,91 +327,6 @@ public async Task ThrowOnInvalidFormatAsync(string json, Type typeToConvert) Assert.Contains(typeToConvert.ToString(), ex.Message); } - [Theory] - [InlineData(@"{""\u0041"":1}", typeof(Dictionary), "A")] - [InlineData(@"{""\u0066\u006f\u006f"":1}", typeof(Dictionary), MyEnum.Foo)] - [InlineData(@"{""\u0066\u006f\u006f\u002c\u0020\u0062\u0061\u0072"":1}", typeof(Dictionary), MyEnumFlags.Foo | MyEnumFlags.Bar)] - public static void TestUnescapedKeys(string json, Type typeToConvert, object keyAsType) - { - object result = JsonSerializer.Deserialize(json, typeToConvert); - - IDictionary dictionary = (IDictionary)result; - Assert.True(dictionary.Contains(keyAsType)); - } - - [Fact] - public static void TestMoreUnescapedKeys() - { - // Test types that cannot be passed as InlineData in above method. - - Dictionary result = JsonSerializer.Deserialize>(@"{""\u0036bb67e4e-9780-4895-851b-75f72ac34c5a"":1}"); - Guid guid = new Guid("6bb67e4e-9780-4895-851b-75f72ac34c5a"); - Assert.Equal(1, result[guid]); - - Dictionary result2 = JsonSerializer.Deserialize>(@"{""\u0032\u0030\u0030\u0039\u002d\u0030\u0036\u002d\u0031\u0035\u0054\u0031\u0033\u003a\u0034\u0035\u003a\u0033\u0030\u002e\u0030\u0030\u0030\u0030\u0030\u0030\u0030"":1}"); - // 2009-06-15T13:45:30.0000000 - DateTime dateTime = new DateTime(2009, 6, 15, 13, 45, 30, DateTimeKind.Unspecified); - Assert.Equal(1, result2[dateTime]); - - Dictionary result3 = JsonSerializer.Deserialize>(@"{""\u0032\u0030\u0030\u0039\u002d\u0030\u0036\u002d\u0031\u0035\u0054\u0031\u0033\u003a\u0034\u0035\u003a\u0033\u0030\u002e\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u005a"":1}"); - // 2009-06-15T13:45:30.0000000Z - dateTime = new DateTime(2009, 6, 15, 13, 45, 30, DateTimeKind.Utc); - Assert.Equal(1, result3[dateTime]); - - Dictionary result4 = JsonSerializer.Deserialize>(@"{""\u0032\u0030\u0030\u0039\u002d\u0030\u0036\u002d\u0031\u0035\u0054\u0031\u0033\u003a\u0034\u0035\u003a\u0033\u0030\u002e\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u002d\u0030\u0037\u003a\u0030\u0030"":1}"); - // 2009-06-15T13:45:30.0000000-07:00 - DateTimeOffset dateTimeOffset = new DateTimeOffset(2009, 6, 15, 13, 45, 30, new TimeSpan(-7, 0, 0)); - Assert.Equal(1, result4[dateTimeOffset]); - } - - [Theory] - [InlineData(@"{""\u0041"":1}", typeof(Dictionary), "A")] - [InlineData(@"{""\u0066\u006f\u006f"":1}", typeof(Dictionary), MyEnum.Foo)] - [InlineData(@"{""\u0066\u006f\u006f\u002c\u0020\u0062\u0061\u0072"":1}", typeof(Dictionary), MyEnumFlags.Foo | MyEnumFlags.Bar)] - public static async Task TestUnescapedKeysAsync(string json, Type typeToConvert, object keyAsType) - { - byte[] utf8Json = Encoding.UTF8.GetBytes(json); - MemoryStream stream = new MemoryStream(utf8Json); - - object result = await JsonSerializer.DeserializeAsync(stream, typeToConvert); - - IDictionary dictionary = (IDictionary)result; - Assert.True(dictionary.Contains(keyAsType)); - } - - [Fact] - public static async Task TestMoreUnescapedKeyAsync() - { - // Test types that cannot be passed as InlineData in above method. - - byte[] utf8Json = Encoding.UTF8.GetBytes(@"{""\u0036bb67e4e-9780-4895-851b-75f72ac34c5a"":1}"); - MemoryStream stream = new MemoryStream(utf8Json); - Dictionary result = await JsonSerializer.DeserializeAsync>(stream); - Guid myGuid = new Guid("6bb67e4e-9780-4895-851b-75f72ac34c5a"); - Assert.Equal(1, result[myGuid]); - - utf8Json = Encoding.UTF8.GetBytes(@"{""\u0032\u0030\u0030\u0039\u002d\u0030\u0036\u002d\u0031\u0035\u0054\u0031\u0033\u003a\u0034\u0035\u003a\u0033\u0030\u002e\u0030\u0030\u0030\u0030\u0030\u0030\u0030"":1}"); - stream = new MemoryStream(utf8Json); - Dictionary result2 = await JsonSerializer.DeserializeAsync>(stream); - // 2009-06-15T13:45:30.0000000 - DateTime myDate = new DateTime(2009, 6, 15, 13, 45, 30, DateTimeKind.Unspecified); - Assert.Equal(1, result2[myDate]); - - utf8Json = Encoding.UTF8.GetBytes(@"{""\u0032\u0030\u0030\u0039\u002d\u0030\u0036\u002d\u0031\u0035\u0054\u0031\u0033\u003a\u0034\u0035\u003a\u0033\u0030\u002e\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u005a"":1}"); - stream = new MemoryStream(utf8Json); - Dictionary result3 = await JsonSerializer.DeserializeAsync>(stream); - // 2009-06-15T13:45:30.0000000Z - myDate = new DateTime(2009, 6, 15, 13, 45, 30, DateTimeKind.Utc); - Assert.Equal(1, result3[myDate]); - - utf8Json = Encoding.UTF8.GetBytes(@"{""\u0032\u0030\u0030\u0039\u002d\u0030\u0036\u002d\u0031\u0035\u0054\u0031\u0033\u003a\u0034\u0035\u003a\u0033\u0030\u002e\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u002d\u0030\u0037\u003a\u0030\u0030"":1}"); - stream = new MemoryStream(utf8Json); - Dictionary result4 = await JsonSerializer.DeserializeAsync>(stream); - // 2009-06-15T13:45:30.0000000-07:00 - DateTimeOffset dateTimeOffset = new DateTimeOffset(2009, 6, 15, 13, 45, 30, new TimeSpan(-7, 0, 0)); - Assert.Equal(1, result4[dateTimeOffset]); - } - [Fact] public static void TestNotSuportedExceptionIsThrown() { @@ -554,6 +469,19 @@ public void TestEscapedValuesOnDeserialize(string escapedPropertyName, object ex Assert.Equal(1, root[expectedDictionaryKey]); } + [Theory] + [MemberData(nameof(EscapedMemberData))] + public async Task TestEscapedValuesOnDeserializeAsync(string escapedPropertyName, object expectedDictionaryKey, Type dictionaryType) + { + string json = $@"{{""{escapedPropertyName}"":1}}"; + MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); + IDictionary root = (IDictionary)await JsonSerializer.DeserializeAsync(stream, dictionaryType); + + bool containsKey = root.Contains(expectedDictionaryKey); + Assert.True(containsKey); + Assert.Equal(1, root[expectedDictionaryKey]); + } + public static IEnumerable EscapedMemberData => new List { From 7201ccb218b6e8780e10fed6b6532ce6ebc53830 Mon Sep 17 00:00:00 2001 From: David Cantu Date: Wed, 1 Jul 2020 18:57:14 -0700 Subject: [PATCH 12/16] Cache TKey and TValue converters --- .../Collection/DictionaryDefaultConverter.cs | 23 ++++++++++--------- .../DictionaryOfTKeyTValueConverter.cs | 10 ++++---- .../Collection/IDictionaryConverter.cs | 13 +++++------ .../IDictionaryOfTKeyTValueConverter.cs | 6 ++--- ...ReadOnlyDictionaryOfTKeyTValueConverter.cs | 6 ++--- ...mmutableDictionaryOfTKeyTValueConverter.cs | 6 ++--- 6 files changed, 32 insertions(+), 32 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 8d38ef9241eb8..d40ddb0368da9 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 @@ -37,7 +37,8 @@ protected virtual void CreateCollection(ref Utf8JsonReader reader, ref ReadStack // in order to avoid performance regression on already supported types. protected bool IsStringKey = typeof(TKey) == typeof(string); - private JsonConverter? _keyConverter; + protected JsonConverter? _keyConverter; + protected JsonConverter? _valueConverter; protected static JsonConverter GetValueConverter(JsonClassInfo classInfo) { @@ -47,8 +48,8 @@ protected static JsonConverter GetValueConverter(JsonClassInfo classInfo return converter; } - protected JsonConverter GetKeyConverter(JsonSerializerOptions options) - => _keyConverter ??= (JsonConverter)options.GetDictionaryKeyConverter(KeyType); + protected static JsonConverter GetKeyConverter(Type keyType, JsonSerializerOptions options) + => (JsonConverter)options.GetDictionaryKeyConverter(keyType); internal sealed override bool OnTryRead( ref Utf8JsonReader reader, @@ -68,8 +69,8 @@ internal sealed override bool OnTryRead( CreateCollection(ref reader, ref state); - JsonConverter elementConverter = GetValueConverter(state.Current.JsonClassInfo); - if (elementConverter.CanUseDirectReadOrWrite) + JsonConverter valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo); + if (valueConverter.CanUseDirectReadOrWrite) { // Process all elements. while (true) @@ -94,7 +95,7 @@ internal sealed override bool OnTryRead( } else { - JsonConverter keyConverter = GetKeyConverter(options); + JsonConverter keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options); key = keyConverter.ReadWithQuotes(ref reader); unescapedPropertyNameAsString = reader.GetString()!; } @@ -103,7 +104,7 @@ internal sealed override bool OnTryRead( state.Current.JsonPropertyNameAsString = unescapedPropertyNameAsString; // Read the value and add. reader.ReadWithVerify(); - TValue element = elementConverter.Read(ref reader, typeof(TValue), options); + TValue element = valueConverter.Read(ref reader, typeof(TValue), options); Add(key, element!, options, ref state); } } @@ -132,7 +133,7 @@ internal sealed override bool OnTryRead( } else { - JsonConverter keyConverter = GetKeyConverter(options); + JsonConverter keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options); key = keyConverter.ReadWithQuotes(ref reader); unescapedPropertyNameAsString = reader.GetString()!; } @@ -142,7 +143,7 @@ internal sealed override bool OnTryRead( reader.ReadWithVerify(); // Get the value from the converter and add it. - elementConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValue element); + valueConverter.TryRead(ref reader, typeof(TValue), options, ref state, out TValue element); Add(key, element!, options, ref state); } } @@ -200,7 +201,7 @@ internal sealed override bool OnTryRead( } // Process all elements. - JsonConverter elementConverter = GetValueConverter(state.Current.JsonClassInfo); + JsonConverter elementConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo); while (true) { if (state.Current.PropertyState == StackFramePropertyState.None) @@ -246,7 +247,7 @@ internal sealed override bool OnTryRead( } else { - JsonConverter keyConverter = GetKeyConverter(options); + JsonConverter keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options); key = keyConverter.ReadWithQuotes(ref reader); unescapedPropertyNameAsString = reader.GetString()!; } 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 d06ad4aae37ea..b34294f9e002f 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 @@ -50,9 +50,9 @@ protected internal override bool OnWriteResume( enumerator = (Dictionary.Enumerator)state.Current.CollectionEnumerator; } - JsonConverter keyConverter = GetKeyConverter(options); - JsonConverter converter = GetValueConverter(state.Current.JsonClassInfo); - if (!state.SupportContinuation && converter.CanUseDirectReadOrWrite) + JsonConverter keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options); + JsonConverter valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo); + if (!state.SupportContinuation && valueConverter.CanUseDirectReadOrWrite) { // Fast path that avoids validation and extra indirection. do @@ -60,7 +60,7 @@ protected internal override bool OnWriteResume( TKey key = enumerator.Current.Key; keyConverter.WriteWithQuotes(writer, key, options, ref state); - converter.Write(writer, enumerator.Current.Value, options); + valueConverter.Write(writer, enumerator.Current.Value, options); } while (enumerator.MoveNext()); } else @@ -82,7 +82,7 @@ protected internal override bool OnWriteResume( } TValue element = enumerator.Current.Value; - if (!converter.TryWrite(writer, element, options, ref state)) + if (!valueConverter.TryWrite(writer, element, options, ref state)) { state.Current.CollectionEnumerator = enumerator; return false; 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 aef50278a8097..1e586c093fa1b 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 @@ -4,7 +4,6 @@ using System.Collections; using System.Collections.Generic; -using System.Diagnostics; namespace System.Text.Json.Serialization.Converters { @@ -23,8 +22,8 @@ protected override void Add(string key, in object? value, JsonSerializerOptions private JsonConverter? _objectConverter; - private JsonConverter GetObjectKeyConverter(JsonSerializerOptions options) - => _objectConverter ??= (JsonConverter)options.GetDictionaryKeyConverter(typeof(object)); + private static JsonConverter GetObjectKeyConverter(JsonSerializerOptions options) + => (JsonConverter)options.GetDictionaryKeyConverter(typeof(object)); protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStack state) { @@ -73,7 +72,7 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio enumerator = (IDictionaryEnumerator)state.Current.CollectionEnumerator; } - JsonConverter converter = GetValueConverter(state.Current.JsonClassInfo); + JsonConverter valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo); do { if (ShouldFlush(writer, ref state)) @@ -89,20 +88,20 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio // Optimize for string since that's the hot path. if (key is string keyString) { - JsonConverter stringKeyConverter = GetKeyConverter(options); + JsonConverter stringKeyConverter = _keyConverter ??= GetKeyConverter(KeyType, options); stringKeyConverter.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 on deserialization. - JsonConverter objectKeyConverter = GetObjectKeyConverter(options); + JsonConverter objectKeyConverter = _objectConverter ??= GetObjectKeyConverter(options); objectKeyConverter.WriteWithQuotes(writer, key, options, ref state); } } object? element = enumerator.Value; - if (!converter.TryWrite(writer, element, options, ref state)) + if (!valueConverter.TryWrite(writer, element, options, ref state)) { state.Current.CollectionEnumerator = enumerator; return false; 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 3fb7de97f8ebc..158feccb0270c 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 @@ -71,8 +71,8 @@ protected internal override bool OnWriteResume( enumerator = (IEnumerator>)state.Current.CollectionEnumerator; } - JsonConverter keyConverter = GetKeyConverter(options); - JsonConverter converter = GetValueConverter(state.Current.JsonClassInfo); + JsonConverter keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options); + JsonConverter valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo); do { if (ShouldFlush(writer, ref state)) @@ -89,7 +89,7 @@ protected internal override bool OnWriteResume( } TValue element = enumerator.Current.Value; - if (!converter.TryWrite(writer, element, options, ref state)) + if (!valueConverter.TryWrite(writer, element, options, ref state)) { state.Current.CollectionEnumerator = enumerator; return false; 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 72c37ffde7eab..b4cc76d8bd6b8 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 @@ -42,8 +42,8 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio enumerator = (Dictionary.Enumerator)state.Current.CollectionEnumerator; } - JsonConverter keyConverter = GetKeyConverter(options); - JsonConverter converter = GetValueConverter(state.Current.JsonClassInfo); + JsonConverter keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options); + JsonConverter valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo); do { if (ShouldFlush(writer, ref state)) @@ -61,7 +61,7 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio } TValue element = enumerator.Current.Value; - if (!converter.TryWrite(writer, element, options, ref state)) + if (!valueConverter.TryWrite(writer, element, options, ref state)) { state.Current.CollectionEnumerator = enumerator; return false; 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 6f75711cc98ef..7d5b429d287d9 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 @@ -53,8 +53,8 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio enumerator = (IEnumerator>)state.Current.CollectionEnumerator; } - JsonConverter keyConverter = GetKeyConverter(options); - JsonConverter converter = GetValueConverter(state.Current.JsonClassInfo); + JsonConverter keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options); + JsonConverter valueConverter = _valueConverter ??= GetValueConverter(state.Current.JsonClassInfo); do { if (ShouldFlush(writer, ref state)) @@ -72,7 +72,7 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio } TValue element = enumerator.Current.Value; - if (!converter.TryWrite(writer, element, options, ref state)) + if (!valueConverter.TryWrite(writer, element, options, ref state)) { state.Current.CollectionEnumerator = enumerator; return false; From 2b30299f1063c0eba9778b5cee12b31e973a5606 Mon Sep 17 00:00:00 2001 From: David Cantu Date: Thu, 2 Jul 2020 00:52:49 -0700 Subject: [PATCH 13/16] Remove duplicated code used to read the dictionary key --- .../Collection/DictionaryDefaultConverter.cs | 73 +++++++------------ 1 file changed, 26 insertions(+), 47 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 d40ddb0368da9..afd541ccd7d0e 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 @@ -86,22 +86,8 @@ internal sealed override bool OnTryRead( // Read method would have thrown if otherwise. Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); - TKey key; - string unescapedPropertyNameAsString; - if (IsStringKey) - { - unescapedPropertyNameAsString = reader.GetString()!; - key = (TKey)(object)unescapedPropertyNameAsString; - } - else - { - JsonConverter keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options); - key = keyConverter.ReadWithQuotes(ref reader); - unescapedPropertyNameAsString = reader.GetString()!; - } + TKey key = ReadDictionaryKey(ref reader, ref state); - // Copy key name for JSON Path support in case of error. - state.Current.JsonPropertyNameAsString = unescapedPropertyNameAsString; // Read the value and add. reader.ReadWithVerify(); TValue element = valueConverter.Read(ref reader, typeof(TValue), options); @@ -124,22 +110,8 @@ internal sealed override bool OnTryRead( // Read method would have thrown if otherwise. Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); - TKey key; - string unescapedPropertyNameAsString; - if (IsStringKey) - { - unescapedPropertyNameAsString = reader.GetString()!; - key = (TKey)(object)unescapedPropertyNameAsString; - } - else - { - JsonConverter keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options); - key = keyConverter.ReadWithQuotes(ref reader); - unescapedPropertyNameAsString = reader.GetString()!; - } + TKey key = ReadDictionaryKey(ref reader, ref state); - // Copy key name for JSON Path support in case of error. - state.Current.JsonPropertyNameAsString = unescapedPropertyNameAsString; reader.ReadWithVerify(); // Get the value from the converter and add it. @@ -238,23 +210,7 @@ internal sealed override bool OnTryRead( } } - TKey key; - string unescapedPropertyNameAsString; - if (IsStringKey) - { - unescapedPropertyNameAsString = reader.GetString()!; - key = (TKey)(object)unescapedPropertyNameAsString; - } - else - { - JsonConverter keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options); - key = keyConverter.ReadWithQuotes(ref reader); - unescapedPropertyNameAsString = reader.GetString()!; - } - - // Copy key name for JSON Path support in case of error. - state.Current.JsonPropertyNameAsString = unescapedPropertyNameAsString; - state.Current.DictionaryKey = key; + state.Current.DictionaryKey = ReadDictionaryKey(ref reader, ref state); } if (state.Current.PropertyState < StackFramePropertyState.ReadValue) @@ -288,6 +244,29 @@ internal sealed override bool OnTryRead( ConvertCollection(ref state, options); value = (TCollection)state.Current.ReturnValue!; return true; + + TKey ReadDictionaryKey(ref Utf8JsonReader reader, ref ReadStack state) + { + TKey key; + string unescapedPropertyNameAsString; + + // Special case string to avoid calling GetString twice and save one allocation. + if (IsStringKey) + { + unescapedPropertyNameAsString = reader.GetString()!; + key = (TKey)(object)unescapedPropertyNameAsString; + } + else + { + JsonConverter keyConverter = _keyConverter ??= GetKeyConverter(KeyType, options); + key = keyConverter.ReadWithQuotes(ref reader); + unescapedPropertyNameAsString = reader.GetString()!; + } + + // Copy key name for JSON Path support in case of error. + state.Current.JsonPropertyNameAsString = unescapedPropertyNameAsString; + return key; + } } internal sealed override bool OnTryWrite( From ee179917aa469292bb4083f26c1d1bb4b1fd289a Mon Sep 17 00:00:00 2001 From: David Cantu Date: Thu, 2 Jul 2020 02:15:20 -0700 Subject: [PATCH 14/16] Bring back TryGet*Core --- .../Text/Json/Reader/Utf8JsonReader.TryGet.cs | 165 +++++++++++------- 1 file changed, 99 insertions(+), 66 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs index a37d03c6057ac..a81a971a24234 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs @@ -142,14 +142,11 @@ public byte GetByte() internal byte GetByteWithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - - if (Utf8Parser.TryParse(span, out byte tmp, out int bytesConsumed) - && span.Length == bytesConsumed) + if (!TryGetByteCore(out byte value, span)) { - return tmp; + throw ThrowHelper.GetFormatException(NumericType.Byte); } - - throw ThrowHelper.GetFormatException(NumericType.Byte); + return value; } /// @@ -180,14 +177,11 @@ public sbyte GetSByte() internal sbyte GetSByteWithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - - if (Utf8Parser.TryParse(span, out sbyte tmp, out int bytesConsumed) - && span.Length == bytesConsumed) + if (!TryGetSByteCore(out sbyte value, span)) { - return tmp; + throw ThrowHelper.GetFormatException(NumericType.SByte); } - - throw ThrowHelper.GetFormatException(NumericType.SByte); + return value; } /// @@ -217,14 +211,11 @@ public short GetInt16() internal short GetInt16WithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - - if (Utf8Parser.TryParse(span, out short tmp, out int bytesConsumed) - && span.Length == bytesConsumed) + if (!TryGetInt16Core(out short value, span)) { - return tmp; + throw ThrowHelper.GetFormatException(NumericType.Int16); } - - throw ThrowHelper.GetFormatException(NumericType.Int16); + return value; } /// @@ -254,14 +245,11 @@ public int GetInt32() internal int GetInt32WithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - - if (Utf8Parser.TryParse(span, out int tmp, out int bytesConsumed) - && span.Length == bytesConsumed) + if (!TryGetInt32Core(out int value, span)) { - return tmp; + throw ThrowHelper.GetFormatException(NumericType.Int32); } - - throw ThrowHelper.GetFormatException(NumericType.Int32); + return value; } /// @@ -291,14 +279,11 @@ public long GetInt64() internal long GetInt64WithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - - if (Utf8Parser.TryParse(span, out long tmp, out int bytesConsumed) - && span.Length == bytesConsumed) + if (!TryGetInt64Core(out long value, span)) { - return tmp; + throw ThrowHelper.GetFormatException(NumericType.Int64); } - - throw ThrowHelper.GetFormatException(NumericType.Int64); + return value; } /// @@ -329,14 +314,11 @@ public ushort GetUInt16() internal ushort GetUInt16WithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - - if (Utf8Parser.TryParse(span, out ushort tmp, out int bytesConsumed) - && span.Length == bytesConsumed) + if (!TryGetUInt16Core(out ushort value, span)) { - return tmp; + throw ThrowHelper.GetFormatException(NumericType.UInt16); } - - throw ThrowHelper.GetFormatException(NumericType.UInt16); + return value; } /// @@ -367,14 +349,11 @@ public uint GetUInt32() internal uint GetUInt32WithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - - if (Utf8Parser.TryParse(span, out uint tmp, out int bytesConsumed) - && span.Length == bytesConsumed) + if (!TryGetUInt32Core(out uint value, span)) { - return tmp; + throw ThrowHelper.GetFormatException(NumericType.UInt32); } - - throw ThrowHelper.GetFormatException(NumericType.UInt32); + return value; } /// @@ -405,14 +384,11 @@ public ulong GetUInt64() internal ulong GetUInt64WithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - - if (Utf8Parser.TryParse(span, out ulong tmp, out int bytesConsumed) - && span.Length == bytesConsumed) + if (!TryGetUInt64Core(out ulong value, span)) { - return tmp; + throw ThrowHelper.GetFormatException(NumericType.UInt64); } - - throw ThrowHelper.GetFormatException(NumericType.UInt64); + return value; } /// @@ -441,14 +417,11 @@ public float GetSingle() internal float GetSingleWithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - - if (Utf8Parser.TryParse(span, out float tmp, out int bytesConsumed, _numberFormat) - && span.Length == bytesConsumed) + if (!TryGetSingleCore(out float value, span)) { - return tmp; + throw ThrowHelper.GetFormatException(NumericType.Single); } - - throw ThrowHelper.GetFormatException(NumericType.Single); + return value; } /// @@ -477,14 +450,11 @@ public double GetDouble() internal double GetDoubleWithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - - if (Utf8Parser.TryParse(span, out double tmp, out int bytesConsumed, _numberFormat) - && span.Length == bytesConsumed) + if (!TryGetDoubleCore(out double value, span)) { - return tmp; + throw ThrowHelper.GetFormatException(NumericType.Double); } - - throw ThrowHelper.GetFormatException(NumericType.Double); + return value; } /// @@ -513,14 +483,11 @@ public decimal GetDecimal() internal decimal GetDecimalWithQuotes() { ReadOnlySpan span = GetUnescapedSpan(); - - if (Utf8Parser.TryParse(span, out decimal tmp, out int bytesConsumed, _numberFormat) - && span.Length == bytesConsumed) + if (!TryGetDecimalCore(out decimal value, span)) { - return tmp; + throw ThrowHelper.GetFormatException(NumericType.Decimal); } - - throw ThrowHelper.GetFormatException(NumericType.Decimal); + return value; } /// @@ -670,6 +637,12 @@ public bool TryGetByte(out byte value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetByteCore(out value, span); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetByteCore(out byte value, ReadOnlySpan span) + { if (Utf8Parser.TryParse(span, out byte tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -700,6 +673,12 @@ public bool TryGetSByte(out sbyte value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetSByteCore(out value, span); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetSByteCore(out sbyte value, ReadOnlySpan span) + { if (Utf8Parser.TryParse(span, out sbyte tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -729,6 +708,12 @@ public bool TryGetInt16(out short value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetInt16Core(out value, span); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetInt16Core(out short value, ReadOnlySpan span) + { if (Utf8Parser.TryParse(span, out short tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -758,6 +743,12 @@ public bool TryGetInt32(out int value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetInt32Core(out value, span); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetInt32Core(out int value, ReadOnlySpan span) + { if (Utf8Parser.TryParse(span, out int tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -787,6 +778,12 @@ public bool TryGetInt64(out long value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetInt64Core(out value, span); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetInt64Core(out long value, ReadOnlySpan span) + { if (Utf8Parser.TryParse(span, out long tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -817,6 +814,12 @@ public bool TryGetUInt16(out ushort value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetUInt16Core(out value, span); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetUInt16Core(out ushort value, ReadOnlySpan span) + { if (Utf8Parser.TryParse(span, out ushort tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -847,6 +850,12 @@ public bool TryGetUInt32(out uint value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetUInt32Core(out value, span); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetUInt32Core(out uint value, ReadOnlySpan span) + { if (Utf8Parser.TryParse(span, out uint tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -877,6 +886,12 @@ public bool TryGetUInt64(out ulong value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetUInt64Core(out value, span); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetUInt64Core(out ulong value, ReadOnlySpan span) + { if (Utf8Parser.TryParse(span, out ulong tmp, out int bytesConsumed) && span.Length == bytesConsumed) { @@ -906,6 +921,12 @@ public bool TryGetSingle(out float value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetSingleCore(out value, span); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetSingleCore(out float value, ReadOnlySpan span) + { if (Utf8Parser.TryParse(span, out float tmp, out int bytesConsumed, _numberFormat) && span.Length == bytesConsumed) { @@ -935,6 +956,12 @@ public bool TryGetDouble(out double value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetDoubleCore(out value, span); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetDoubleCore(out double value, ReadOnlySpan span) + { if (Utf8Parser.TryParse(span, out double tmp, out int bytesConsumed, _numberFormat) && span.Length == bytesConsumed) { @@ -964,6 +991,12 @@ public bool TryGetDecimal(out decimal value) } ReadOnlySpan span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan; + return TryGetDecimalCore(out value, span); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetDecimalCore(out decimal value, ReadOnlySpan span) + { if (Utf8Parser.TryParse(span, out decimal tmp, out int bytesConsumed, _numberFormat) && span.Length == bytesConsumed) { From 8cd787bb71a9f8f353c3d4bb4545b2424d7c34d1 Mon Sep 17 00:00:00 2001 From: David Cantu Date: Thu, 2 Jul 2020 02:16:54 -0700 Subject: [PATCH 15/16] Fix test errors with floating point types --- ...CollectionTests.Dictionary.NonStringKey.cs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) 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 24ab65fe26e1e..051c779d0334f 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 @@ -98,12 +98,14 @@ public class DictionaryDateTimeOffsetKey : DictionaryKeyTestsBase { + protected override string _expectedJson => $@"{{""{JsonSerializer.Serialize(decimal.MaxValue)}"":1}}"; protected override decimal Key => decimal.MaxValue; protected override int Value => 1; } public class DictionaryDoubleKey : DictionaryKeyTestsBase { + protected override string _expectedJson => $@"{{""{JsonSerializer.Serialize(double.MaxValue)}"":1}}"; protected override double Key => double.MaxValue; protected override int Value => 1; } @@ -153,6 +155,7 @@ public class DictionarySByteKey : DictionaryKeyTestsBase public class DictionarySingleKey : DictionaryKeyTestsBase { + protected override string _expectedJson => $@"{{""{JsonSerializer.Serialize(float.MaxValue)}"":1}}"; protected override float Key => float.MaxValue; protected override int Value => 1; } @@ -452,9 +455,9 @@ public void EnsureNonStringKeysDontGetEscapedOnSerialize(object key, string expe public static IEnumerable DictionaryKeysWithSpecialCharacters => new List { - new object[] { float.MaxValue, "3.4028235E+38" }, - new object[] { double.MaxValue, "1.7976931348623157E+308" }, - new object[] { DateTimeOffset.MaxValue, "9999-12-31T23:59:59.9999999+00:00" } + new object[] { float.MaxValue, JsonSerializer.Serialize(float.MaxValue) }, + new object[] { double.MaxValue, JsonSerializer.Serialize(double.MaxValue) }, + new object[] { DateTimeOffset.MaxValue, JsonSerializer.Serialize(DateTimeOffset.MaxValue) } }; [Theory] @@ -501,12 +504,13 @@ public async Task TestEscapedValuesOnDeserializeAsync(string escapedPropertyName long.MaxValue, typeof(Dictionary) }, new object[] { @"\u0031\u0038\u0034\u0034\u0036\u0037\u0034\u0034\u0030\u0037\u0033\u0037\u0030\u0039\u0035\u0035\u0031\u0036\u0031\u0035", ulong.MaxValue, typeof(Dictionary) }, - new object[] { @"\u0033\u002e\u0034\u0030\u0032\u0038\u0032\u0033\u0034\u0037\u0045\u002b\u0033\u0038", - float.MaxValue, typeof(Dictionary) }, - new object[] { @"\u0031\u002e\u0037\u0039\u0037\u0036\u0039\u0033\u0031\u0033\u0034\u0038\u0036\u0032\u0033\u0031\u0035\u0037\u0045\u002b\u0033\u0030\u0038", - double.MaxValue, typeof(Dictionary) }, - new object[] { @"\u0037\u0039\u0032\u0032\u0038\u0031\u0036\u0032\u0035\u0031\u0034\u0032\u0036\u0034\u0033\u0033\u0037\u0035\u0039\u0033\u0035\u0034\u0033\u0039\u0035\u0030\u0033\u0033\u0035", - decimal.MaxValue, typeof(Dictionary) }, + // Do not use max values on floating point types since it may have different string representations depending on the tfm. + new object[] { @"\u0033\u002e\u0031\u0032\u0035\u0065\u0037", + 3.125e7f, typeof(Dictionary) }, + new object[] { @"\u0033\u002e\u0031\u0032\u0035\u0065\u0037", + 3.125e7d, typeof(Dictionary) }, + new object[] { @"\u0033\u002e\u0031\u0032\u0035\u0065\u0037", + 3.125e7m, typeof(Dictionary) }, new object[] { @"\u0039\u0039\u0039\u0039\u002d\u0031\u0032\u002d\u0033\u0031\u0054\u0032\u0033\u003a\u0035\u0039\u003a\u0035\u0039\u002e\u0039\u0039\u0039\u0039\u0039\u0039\u0039", DateTime.MaxValue, typeof(Dictionary) }, new object[] { @"\u0039\u0039\u0039\u0039\u002d\u0031\u0032\u002d\u0033\u0031\u0054\u0032\u0033\u003a\u0035\u0039\u003a\u0035\u0039\u002e\u0039\u0039\u0039\u0039\u0039\u0039\u0039\u002b\u0030\u0030\u003a\u0030\u0030", From 8b30e75791c027ffef3de02bef8ff4d9b86e9625 Mon Sep 17 00:00:00 2001 From: David Cantu Date: Mon, 6 Jul 2020 14:05:01 -0700 Subject: [PATCH 16/16] Address nits and feedback from steveharter --- .../JsonSerializerOptions.Converters.cs | 46 +++++++++---------- ...CollectionTests.Dictionary.NonStringKey.cs | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) 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 c8db4408709f0..fc63f11fb7ca0 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 @@ -75,12 +75,10 @@ void Add(JsonConverter converter) => internal JsonConverter GetDictionaryKeyConverter(Type keyType) { - if (_dictionaryKeyConverters == null) - { - _dictionaryKeyConverters = GetDictionaryKeyConverters(); - } + _dictionaryKeyConverters ??= GetDictionaryKeyConverters(); - if (!_dictionaryKeyConverters.TryGetValue(keyType, out JsonConverter? converter)){ + if (!_dictionaryKeyConverters.TryGetValue(keyType, out JsonConverter? converter)) + { if (keyType.IsEnum) { converter = GetEnumConverter(); @@ -94,6 +92,8 @@ internal JsonConverter GetDictionaryKeyConverter(Type 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() => (JsonConverter)Activator.CreateInstance( typeof(EnumConverter<>).MakeGenericType(keyType), @@ -111,24 +111,24 @@ private static ConcurrentDictionary GetDictionaryKeyConvert var converters = new ConcurrentDictionary(Environment.ProcessorCount, NumberOfConverters); // When adding to this, update NumberOfConverters above. - Add(new BooleanConverter()); - Add(new ByteConverter()); - Add(new CharConverter()); - Add(new DateTimeConverter()); - Add(new DateTimeOffsetConverter()); - Add(new DoubleConverter()); - Add(new DecimalConverter()); - Add(new GuidConverter()); - Add(new Int16Converter()); - Add(new Int32Converter()); - Add(new Int64Converter()); - Add(new ObjectConverter()); - Add(new SByteConverter()); - Add(new SingleConverter()); - Add(new StringConverter()); - Add(new UInt16Converter()); - Add(new UInt32Converter()); - Add(new UInt64Converter()); + 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); 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 051c779d0334f..98b96d5d95a0f 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 @@ -35,7 +35,7 @@ private Dictionary BuildDictionary() } [Fact] - public void TestDictinaryKey() + public void TestDictionaryKey() { Dictionary dictionary = BuildDictionary();