diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/CastingConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/CastingConverter.cs index fb3dad041fc70..9318301476a5e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/CastingConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/CastingConverter.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Text.Json.Reflection; namespace System.Text.Json.Serialization.Converters @@ -9,18 +10,18 @@ namespace System.Text.Json.Serialization.Converters /// /// Converter wrapper which casts SourceType into TargetType /// - internal sealed class CastingConverter : JsonConverter + internal sealed class CastingConverter : JsonConverter { - private readonly JsonConverter _sourceConverter; + private readonly JsonConverter _sourceConverter; internal override Type? KeyType => _sourceConverter.KeyType; internal override Type? ElementType => _sourceConverter.ElementType; public override bool HandleNull { get; } internal override bool SupportsCreateObjectDelegate => _sourceConverter.SupportsCreateObjectDelegate; - internal CastingConverter(JsonConverter sourceConverter) + internal CastingConverter(JsonConverter sourceConverter, bool handleNull, bool handleNullOnRead, bool handleNullOnWrite) { - Debug.Assert(typeof(T).IsInSubtypeRelationshipWith(typeof(TSource))); + Debug.Assert(typeof(T).IsInSubtypeRelationshipWith(sourceConverter.TypeToConvert)); Debug.Assert(sourceConverter.SourceConverterForCastingConverter is null, "casting converters should not be layered."); _sourceConverter = sourceConverter; @@ -30,83 +31,45 @@ internal CastingConverter(JsonConverter sourceConverter) CanBePolymorphic = sourceConverter.CanBePolymorphic; // Ensure HandleNull values reflect the exact configuration of the source converter - HandleNullOnRead = sourceConverter.HandleNullOnRead; - HandleNullOnWrite = sourceConverter.HandleNullOnWrite; - HandleNull = sourceConverter.HandleNull; + HandleNullOnRead = handleNullOnRead; + HandleNullOnWrite = handleNullOnWrite; + HandleNull = handleNull; } internal override JsonConverter? SourceConverterForCastingConverter => _sourceConverter; public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - => CastOnRead(_sourceConverter.Read(ref reader, typeToConvert, options)); + => JsonSerializer.UnboxOnRead(_sourceConverter.ReadAsObject(ref reader, typeToConvert, options)); public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) - => _sourceConverter.Write(writer, CastOnWrite(value), options); + => _sourceConverter.WriteAsObject(writer, value, options); internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, scoped ref ReadStack state, out T? value) { - bool result = _sourceConverter.OnTryRead(ref reader, typeToConvert, options, ref state, out TSource? sourceValue); - value = CastOnRead(sourceValue); + bool result = _sourceConverter.OnTryReadAsObject(ref reader, typeToConvert, options, ref state, out object? sourceValue); + value = JsonSerializer.UnboxOnRead(sourceValue); return result; } internal override bool OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializerOptions options, ref WriteStack state) - => _sourceConverter.OnTryWrite(writer, CastOnWrite(value), options, ref state); + => _sourceConverter.OnTryWriteAsObject(writer, value, options, ref state); public override T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - => CastOnRead(_sourceConverter.ReadAsPropertyName(ref reader, typeToConvert, options)); + => JsonSerializer.UnboxOnRead(_sourceConverter.ReadAsPropertyNameAsObject(ref reader, typeToConvert, options))!; internal override T ReadAsPropertyNameCore(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - => CastOnRead(_sourceConverter.ReadAsPropertyNameCore(ref reader, typeToConvert, options)); + => JsonSerializer.UnboxOnRead(_sourceConverter.ReadAsPropertyNameCoreAsObject(ref reader, typeToConvert, options))!; public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options) - => _sourceConverter.WriteAsPropertyName(writer, CastOnWrite(value), options); + => _sourceConverter.WriteAsPropertyNameAsObject(writer, value, options); internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, T value, JsonSerializerOptions options, bool isWritingExtensionDataProperty) - => _sourceConverter.WriteAsPropertyNameCore(writer, CastOnWrite(value), options, isWritingExtensionDataProperty); + => _sourceConverter.WriteAsPropertyNameCoreAsObject(writer, value, options, isWritingExtensionDataProperty); internal override T ReadNumberWithCustomHandling(ref Utf8JsonReader reader, JsonNumberHandling handling, JsonSerializerOptions options) - => CastOnRead(_sourceConverter.ReadNumberWithCustomHandling(ref reader, handling, options)); + => JsonSerializer.UnboxOnRead(_sourceConverter.ReadNumberWithCustomHandlingAsObject(ref reader, handling, options))!; internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, T value, JsonNumberHandling handling) - => _sourceConverter.WriteNumberWithCustomHandling(writer, CastOnWrite(value), handling); - - private static T CastOnRead(TSource? source) - { - if (default(T) is null && default(TSource) is null && source is null) - { - return default!; - } - - if (source is T t) - { - return t; - } - - HandleFailure(source); - return default!; - - static void HandleFailure(TSource? source) - { - if (source is null) - { - ThrowHelper.ThrowInvalidOperationException_DeserializeUnableToAssignNull(typeof(T)); - } - else - { - ThrowHelper.ThrowInvalidCastException_DeserializeUnableToAssignValue(typeof(TSource), typeof(T)); - } - } - } - - private static TSource CastOnWrite(T source) - { - if (default(TSource) is not null && default(T) is null && source is null) - { - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(typeof(TSource)); - } - - return (TSource)(object?)source!; - } + => _sourceConverter.WriteNumberWithCustomHandlingAsObject(writer, value, handling); } } 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 c2d25718ba63b..d7153aeb94427 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 @@ -166,7 +166,7 @@ internal override bool OnTryRead( ResolvePolymorphicConverter(jsonTypeInfo, ref state) is JsonConverter polymorphicConverter) { Debug.Assert(!IsValueType); - bool success = polymorphicConverter.OnTryReadAsObject(ref reader, options, ref state, out object? objectResult); + bool success = polymorphicConverter.OnTryReadAsObject(ref reader, polymorphicConverter.TypeToConvert, options, ref state, out object? objectResult); value = (TCollection)objectResult!; state.ExitPolymorphicConverter(success); return success; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonDictionaryConverter.cs index 0475b0a8848df..e1dbc744f2180 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonDictionaryConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonDictionaryConverter.cs @@ -190,7 +190,7 @@ internal sealed override bool OnTryRead( ResolvePolymorphicConverter(jsonTypeInfo, ref state) is JsonConverter polymorphicConverter) { Debug.Assert(!IsValueType); - bool success = polymorphicConverter.OnTryReadAsObject(ref reader, options, ref state, out object? objectResult); + bool success = polymorphicConverter.OnTryReadAsObject(ref reader, polymorphicConverter.TypeToConvert, options, ref state, out object? objectResult); value = (TDictionary)objectResult!; state.ExitPolymorphicConverter(success); return success; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs index c3082bde9c419..f426e38217182 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs @@ -107,7 +107,7 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, ResolvePolymorphicConverter(jsonTypeInfo, ref state) is JsonConverter polymorphicConverter) { Debug.Assert(!IsValueType); - bool success = polymorphicConverter.OnTryReadAsObject(ref reader, options, ref state, out object? objectResult); + bool success = polymorphicConverter.OnTryReadAsObject(ref reader, polymorphicConverter.TypeToConvert, options, ref state, out object? objectResult); value = (T)objectResult!; state.ExitPolymorphicConverter(success); return success; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.cs index f87356505ad78..c7ba3c959e32b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.cs @@ -19,7 +19,7 @@ protected sealed override bool ReadAndCacheConstructorArgument(scoped ref ReadSt Debug.Assert(jsonParameterInfo.ShouldDeserialize); Debug.Assert(jsonParameterInfo.Options != null); - bool success = jsonParameterInfo.ConverterBase.TryReadAsObject(ref reader, jsonParameterInfo.Options!, ref state, out object? arg); + bool success = jsonParameterInfo.ConverterBase.TryReadAsObject(ref reader, TypeToConvert, jsonParameterInfo.Options!, ref state, out object? arg); if (success && !(arg == null && jsonParameterInfo.IgnoreNullTokensOnRead)) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs index d373ff9dfab6d..5a7f33e704726 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs @@ -137,7 +137,7 @@ internal sealed override bool OnTryRead(ref Utf8JsonReader reader, Type typeToCo ResolvePolymorphicConverter(jsonTypeInfo, ref state) is JsonConverter polymorphicConverter) { Debug.Assert(!IsValueType); - bool success = polymorphicConverter.OnTryReadAsObject(ref reader, options, ref state, out object? objectResult); + bool success = polymorphicConverter.OnTryReadAsObject(ref reader, polymorphicConverter.TypeToConvert, options, ref state, out object? objectResult); value = (T)objectResult!; state.ExitPolymorphicConverter(success); return success; 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 0fc70fd5b9818..ed2d884876c84 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 @@ -110,6 +110,11 @@ internal virtual JsonTypeInfo CreateCustomJsonTypeInfo(JsonSerializerOptions opt internal abstract JsonConverter CreateCastingConverter(); + /// + /// Set if this converter is itself a casting converter. + /// + internal virtual JsonConverter? SourceConverterForCastingConverter => null; + internal abstract Type? ElementType { get; } internal abstract Type? KeyType { get; } @@ -138,15 +143,20 @@ internal static bool ShouldFlush(Utf8JsonWriter writer, ref WriteStack state) // This is used internally to quickly determine the type being converted for JsonConverter. internal abstract Type TypeToConvert { get; } - internal abstract bool OnTryReadAsObject(ref Utf8JsonReader reader, JsonSerializerOptions options, scoped ref ReadStack state, out object? value); - internal abstract bool TryReadAsObject(ref Utf8JsonReader reader, JsonSerializerOptions options, scoped ref ReadStack state, out object? value); + internal abstract object? ReadAsObject(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options); + internal abstract bool OnTryReadAsObject(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, scoped ref ReadStack state, out object? value); + internal abstract bool TryReadAsObject(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, scoped ref ReadStack state, out object? value); + internal abstract object? ReadAsPropertyNameAsObject(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options); + internal abstract object? ReadAsPropertyNameCoreAsObject(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options); + internal abstract object? ReadNumberWithCustomHandlingAsObject(ref Utf8JsonReader reader, JsonNumberHandling handling, JsonSerializerOptions options); + internal abstract void WriteAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options); + internal abstract bool OnTryWriteAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state); internal abstract bool TryWriteAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state); + internal abstract void WriteAsPropertyNameAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options); + internal abstract void WriteAsPropertyNameCoreAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, bool isWritingExtensionDataProperty); + internal abstract void WriteNumberWithCustomHandlingAsObject(Utf8JsonWriter writer, object? value, JsonNumberHandling handling); - /// - /// Loosely-typed WriteToPropertyName() that forwards to strongly-typed WriteToPropertyName(). - /// - internal abstract void WriteAsPropertyNameCoreAsObject(Utf8JsonWriter writer, object value, JsonSerializerOptions options, bool isWritingExtensionDataProperty); // Whether a type (ConverterStrategy.Object) is deserialized using a parameterized constructor. internal virtual bool ConstructorIsParameterized { get; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs index b5c70e64bc243..441cbf5eab354 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 @@ -61,8 +61,16 @@ internal JsonConverter GetConverterInternal(Type typeToConvert, JsonSerializerOp return converter; } + internal sealed override object? ReadAsObject(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + Debug.Fail("We should never get here."); + + throw new InvalidOperationException(); + } + internal sealed override bool OnTryReadAsObject( ref Utf8JsonReader reader, + Type typeToConvert, JsonSerializerOptions options, scoped ref ReadStack state, out object? value) @@ -74,6 +82,7 @@ internal sealed override bool OnTryReadAsObject( internal sealed override bool TryReadAsObject( ref Utf8JsonReader reader, + Type typeToConvert, JsonSerializerOptions options, scoped ref ReadStack state, out object? value) @@ -83,6 +92,45 @@ internal sealed override bool TryReadAsObject( throw new InvalidOperationException(); } + internal sealed override object? ReadAsPropertyNameAsObject(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + Debug.Fail("We should never get here."); + + throw new InvalidOperationException(); + } + + internal sealed override object? ReadAsPropertyNameCoreAsObject(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + Debug.Fail("We should never get here."); + + throw new InvalidOperationException(); + } + + internal sealed override object? ReadNumberWithCustomHandlingAsObject(ref Utf8JsonReader reader, JsonNumberHandling handling, JsonSerializerOptions options) + { + Debug.Fail("We should never get here."); + + throw new InvalidOperationException(); + } + + internal sealed override void WriteAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) + { + Debug.Fail("We should never get here."); + + throw new InvalidOperationException(); + } + + internal sealed override bool OnTryWriteAsObject( + Utf8JsonWriter writer, + object? value, + JsonSerializerOptions options, + ref WriteStack state) + { + Debug.Fail("We should never get here."); + + throw new InvalidOperationException(); + } + internal sealed override bool TryWriteAsObject( Utf8JsonWriter writer, object? value, @@ -94,10 +142,18 @@ internal sealed override bool TryWriteAsObject( throw new InvalidOperationException(); } + internal sealed override void WriteAsPropertyNameAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) + { + Debug.Fail("We should never get here."); + + throw new InvalidOperationException(); + } + internal sealed override Type TypeToConvert => null!; internal sealed override void WriteAsPropertyNameCoreAsObject( - Utf8JsonWriter writer, object value, + Utf8JsonWriter writer, + object? value, JsonSerializerOptions options, bool isWritingExtensionDataProperty) { @@ -106,6 +162,13 @@ internal sealed override void WriteAsPropertyNameCoreAsObject( throw new InvalidOperationException(); } + internal sealed override void WriteNumberWithCustomHandlingAsObject(Utf8JsonWriter writer, object? value, JsonNumberHandling handling) + { + Debug.Fail("We should never get here."); + + throw new InvalidOperationException(); + } + internal sealed override JsonConverter CreateCastingConverter() { ThrowHelper.ThrowInvalidOperationException_ConverterCanConvertMultipleTypes(typeof(TTarget), this); 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 972a33db7d347..33e3bb93ccca6 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 @@ -80,14 +80,9 @@ internal sealed override JsonConverter CreateCastingConverter( // Avoid layering casting converters by consulting any source converters directly. return SourceConverterForCastingConverter?.CreateCastingConverter() - ?? new CastingConverter(this); + ?? new CastingConverter(this, handleNull: HandleNull, handleNullOnRead: HandleNullOnRead, handleNullOnWrite: HandleNullOnWrite); } - /// - /// Set if this converter is itself a casting converter. - /// - internal virtual JsonConverter? SourceConverterForCastingConverter => null; - internal override Type? KeyType => null; internal override Type? ElementType => null; @@ -128,10 +123,43 @@ public virtual bool HandleNull /// internal bool HandleNullOnWrite { get; private protected set; } + // This non-generic API is sealed as it just forwards to the generic version. + internal sealed override void WriteAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) + { + T valueOfT = JsonSerializer.UnboxOnWrite(value)!; + Write(writer, valueOfT, options); + } + + // This non-generic API is sealed as it just forwards to the generic version. + internal sealed override bool OnTryWriteAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state) + { + T valueOfT = JsonSerializer.UnboxOnWrite(value)!; + return OnTryWrite(writer, valueOfT, options, ref state); + } + + // This non-generic API is sealed as it just forwards to the generic version. + internal sealed override void WriteAsPropertyNameAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) + { + T valueOfT = JsonSerializer.UnboxOnWrite(value)!; + WriteAsPropertyName(writer, valueOfT, options); + } + + internal sealed override void WriteAsPropertyNameCoreAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, bool isWritingExtensionDataProperty) + { + T valueOfT = JsonSerializer.UnboxOnWrite(value)!; + WriteAsPropertyNameCore(writer, valueOfT, options, isWritingExtensionDataProperty); + } + + internal sealed override void WriteNumberWithCustomHandlingAsObject(Utf8JsonWriter writer, object? value, JsonNumberHandling handling) + { + T valueOfT = JsonSerializer.UnboxOnWrite(value)!; + WriteNumberWithCustomHandling(writer, valueOfT, handling); + } + // This non-generic API is sealed as it just forwards to the generic version. internal sealed override bool TryWriteAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state) { - T valueOfT = (T)value!; + T valueOfT = JsonSerializer.UnboxOnWrite(value)!; return TryWrite(writer, valueOfT, options, ref state); } @@ -284,20 +312,44 @@ internal bool TryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSeriali return success; } - internal sealed override bool OnTryReadAsObject(ref Utf8JsonReader reader, JsonSerializerOptions options, scoped ref ReadStack state, out object? value) + internal sealed override bool OnTryReadAsObject(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, scoped ref ReadStack state, out object? value) { - bool success = OnTryRead(ref reader, TypeToConvert, options, ref state, out T? typedValue); + bool success = OnTryRead(ref reader, typeToConvert, options, ref state, out T? typedValue); value = typedValue; return success; } - internal sealed override bool TryReadAsObject(ref Utf8JsonReader reader, JsonSerializerOptions options, scoped ref ReadStack state, out object? value) + internal sealed override bool TryReadAsObject(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, scoped ref ReadStack state, out object? value) { - bool success = TryRead(ref reader, TypeToConvert, options, ref state, out T? typedValue); + bool success = TryRead(ref reader, typeToConvert, options, ref state, out T? typedValue); value = typedValue; return success; } + internal sealed override object? ReadAsObject(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + T? typedValue = Read(ref reader, typeToConvert, options); + return typedValue; + } + + internal sealed override object? ReadAsPropertyNameAsObject(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + T typedValue = ReadAsPropertyName(ref reader, typeToConvert, options); + return typedValue; + } + + internal sealed override object? ReadAsPropertyNameCoreAsObject(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + T typedValue = ReadAsPropertyNameCore(ref reader, typeToConvert, options); + return typedValue; + } + + internal sealed override object? ReadNumberWithCustomHandlingAsObject(ref Utf8JsonReader reader, JsonNumberHandling handling, JsonSerializerOptions options) + { + T typedValue = ReadNumberWithCustomHandling(ref reader, handling, options); + return typedValue; + } + /// /// Performance optimization. /// The 'in' modifier in 'TryWrite(in T Value)' causes boxing for Nullable{T}, so this helper avoids that. @@ -628,9 +680,6 @@ internal virtual void WriteAsPropertyNameCore(Utf8JsonWriter writer, T value, Js } } - internal sealed override void WriteAsPropertyNameCoreAsObject(Utf8JsonWriter writer, object value, JsonSerializerOptions options, bool isWritingExtensionDataProperty) - => WriteAsPropertyNameCore(writer, (T)value, options, isWritingExtensionDataProperty); - // .NET 5 backward compatibility: hardcode the default converter for primitive key serialization. private JsonConverter? GetFallbackConverterForPropertyNameSerialization(JsonSerializerOptions options) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Helpers.cs index b0141c05428d2..d0f80b7d2839a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Helpers.cs @@ -81,5 +81,52 @@ internal static bool IsValidNumberHandlingValue(JsonNumberHandling handling) => internal static bool IsValidUnmappedMemberHandlingValue(JsonUnmappedMemberHandling handling) => handling is JsonUnmappedMemberHandling.Skip or JsonUnmappedMemberHandling.Disallow; + + [return: NotNullIfNotNull(nameof(value))] + internal static T? UnboxOnRead(object? value) + { + if (value is null) + { + if (default(T) is not null) + { + // Casting null values to a non-nullable struct throws NullReferenceException. + ThrowUnableToCastValue(value); + } + + return default; + } + + if (value is T typedValue) + { + return typedValue; + } + + ThrowUnableToCastValue(value); + return default!; + + static void ThrowUnableToCastValue(object? value) + { + if (value is null) + { + ThrowHelper.ThrowInvalidOperationException_DeserializeUnableToAssignNull(declaredType: typeof(T)); + } + else + { + ThrowHelper.ThrowInvalidCastException_DeserializeUnableToAssignValue(typeOfValue: value.GetType(), declaredType: typeof(T)); + } + } + } + + [return: NotNullIfNotNull(nameof(value))] + internal static T? UnboxOnWrite(object? value) + { + if (default(T) is not null && value is null) + { + // Casting null values to a non-nullable struct throws NullReferenceException. + ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(typeof(T)); + } + + return (T?)value; + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.WriteHelpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.WriteHelpers.cs index cf3b8076dd1f5..d16480da79766 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.WriteHelpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.WriteHelpers.cs @@ -277,32 +277,13 @@ rootValue is not null && } internal sealed override void SerializeAsObject(Utf8JsonWriter writer, object? rootValue, bool isInvokedByPolymorphicConverter = false) - => Serialize(writer, UnboxValue(rootValue), rootValue, isInvokedByPolymorphicConverter); + => Serialize(writer, JsonSerializer.UnboxOnWrite(rootValue), rootValue, isInvokedByPolymorphicConverter); internal sealed override Task SerializeAsObjectAsync(Stream utf8Json, object? rootValue, CancellationToken cancellationToken, bool isInvokedByPolymorphicConverter = false) - => SerializeAsync(utf8Json, UnboxValue(rootValue), cancellationToken, rootValue, isInvokedByPolymorphicConverter); + => SerializeAsync(utf8Json, JsonSerializer.UnboxOnWrite(rootValue), cancellationToken, rootValue, isInvokedByPolymorphicConverter); internal sealed override void SerializeAsObject(Stream utf8Json, object? rootValue, bool isInvokedByPolymorphicConverter = false) - => Serialize(utf8Json, UnboxValue(rootValue), rootValue, isInvokedByPolymorphicConverter); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private T? UnboxValue(object? value) - { - if ( -#if NETCOREAPP - // Treated as a constant by recent versions of the JIT. - typeof(T).IsValueType && -#else - Type.IsValueType && -#endif - default(T) is not null && value is null) - { - // Casting null values to a non-nullable struct throws NullReferenceException, replace with JsonException - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type); - } - - return (T?)value; - } + => Serialize(utf8Json, JsonSerializer.UnboxOnWrite(rootValue), rootValue, isInvokedByPolymorphicConverter); // Fast-path serialization in source gen has not been designed with streaming in mind. // Even though it's not used in streaming by default, we can sometimes try to turn it on