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 72f8dd8d71137..0242cb2ab30ad 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 @@ -25,6 +25,7 @@ internal CastingConverter(JsonConverter sourceConverter) : base(initial IsInternalConverterForNumberType = sourceConverter.IsInternalConverterForNumberType; RequiresReadAhead = sourceConverter.RequiresReadAhead; CanUseDirectReadOrWrite = sourceConverter.CanUseDirectReadOrWrite; + CanBePolymorphic = sourceConverter.CanBePolymorphic; } public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) @@ -91,6 +92,11 @@ static void HandleFailure(TSource? source) 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!; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs index d921c7b6e8e98..fd8e6367cd4dd 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs @@ -70,13 +70,12 @@ internal override bool OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializer Debug.Assert(options == jsonTypeInfo.Options); if (!state.SupportContinuation && - jsonTypeInfo.HasSerializeHandler && - jsonTypeInfo is JsonTypeInfo info && - !state.CurrentContainsMetadata && // Do not use the fast path if state needs to write metadata. - info.Options.SerializerContext?.CanUseSerializationLogic == true) + jsonTypeInfo.CanUseSerializeHandler && + !state.CurrentContainsMetadata) // Do not use the fast path if state needs to write metadata. { - Debug.Assert(info.SerializeHandler != null); - info.SerializeHandler(writer, value); + Debug.Assert(jsonTypeInfo is JsonTypeInfo typeInfo && typeInfo.SerializeHandler != null); + Debug.Assert(options.SerializerContext?.CanUseSerializationLogic == true); + ((JsonTypeInfo)jsonTypeInfo).SerializeHandler!(writer, value); return true; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.MetadataHandling.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.MetadataHandling.cs index 15181a2c229fe..a507effee9556 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.MetadataHandling.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.MetadataHandling.cs @@ -80,6 +80,16 @@ public partial class JsonConverter case PolymorphicSerializationState.None: Debug.Assert(!state.IsContinuation); + if (state.IsPolymorphicRootValue && state.CurrentDepth == 0) + { + Debug.Assert(jsonTypeInfo.PolymorphicTypeResolver != null); + + // We're serializing a root-level object value whose runtime type uses type hierarchies. + // For consistency with nested value handling, we want to serialize as-is without emitting metadata. + state.Current.PolymorphicSerializationState = PolymorphicSerializationState.PolymorphicReEntryNotFound; + break; + } + Type runtimeType = value.GetType(); if (jsonTypeInfo.PolymorphicTypeResolver is PolymorphicTypeResolver resolver) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.ReadCore.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.ReadCore.cs index 9c91ac3d7fced..c327ada5fee8f 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.ReadCore.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.ReadCore.cs @@ -28,7 +28,7 @@ public partial class JsonConverter { if (state.SupportContinuation) { - // If a Stream-based scenaio, return the actual value previously found; + // If a Stream-based scenario, return the actual value previously found; // this may or may not be the final pass through here. state.BytesConsumed += reader.BytesConsumed; if (state.Current.ReturnValue == null) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.WriteCore.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.WriteCore.cs index 98fbbcb45b1e4..7b003325d4dae 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.WriteCore.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.WriteCore.cs @@ -13,14 +13,14 @@ internal sealed override bool WriteCoreAsObject( { if ( #if NETCOREAPP - // Short-circuit the check against "is not null"; treated as a constant by recent versions of the JIT. + // Treated as a constant by recent versions of the JIT. typeof(T).IsValueType) #else IsValueType) #endif { // Value types can never have a null except for Nullable. - if (value == null && Nullable.GetUnderlyingType(TypeToConvert) == null) + if (default(T) is not null && value is null) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonResumableConverterOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonResumableConverterOfT.cs index 69287a57279e3..de61e597c2a3f 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonResumableConverterOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonResumableConverterOfT.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text.Json.Serialization.Metadata; + namespace System.Text.Json.Serialization { /// @@ -20,7 +22,9 @@ internal abstract class JsonResumableConverter : JsonConverter // Bridge from resumable to value converters. ReadStack state = default; - state.Initialize(typeToConvert, options, supportContinuation: false); + JsonTypeInfo jsonTypeInfo = options.GetTypeInfoInternal(typeToConvert); + state.Initialize(jsonTypeInfo); + TryRead(ref reader, typeToConvert, options, ref state, out T? value); return value; } @@ -33,9 +37,10 @@ public sealed override void Write(Utf8JsonWriter writer, T value, JsonSerializer } // Bridge from resumable to value converters. - WriteStack state = default; - state.Initialize(typeof(T), options, supportContinuation: false, supportAsync: false); + JsonTypeInfo typeInfo = options.GetTypeInfoInternal(typeof(T)); + state.Initialize(typeInfo); + try { TryWrite(writer, value, options, ref state); 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 c682b282ae711..0e5c16b05bb65 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 @@ -15,31 +15,42 @@ public static partial class JsonSerializer [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)] - private static JsonTypeInfo GetTypeInfo(JsonSerializerOptions? options, Type runtimeType) + private static JsonTypeInfo GetTypeInfo(JsonSerializerOptions? options, Type inputType) { - Debug.Assert(runtimeType != null); + Debug.Assert(inputType != null); options ??= JsonSerializerOptions.Default; - if (!options.IsImmutable || !DefaultJsonTypeInfoResolver.IsDefaultInstanceRooted) + if (!options.IsInitializedForReflectionSerializer) { options.InitializeForReflectionSerializer(); } - return options.GetTypeInfoForRootType(runtimeType); + // In order to improve performance of polymorphic root-level object serialization, + // we bypass GetTypeInfoForRootType and cache JsonTypeInfo in a dedicated property. + // This lets any derived types take advantage of the cache in GetTypeInfoForRootType themselves. + return inputType == JsonTypeInfo.ObjectType + ? options.ObjectTypeInfo + : options.GetTypeInfoForRootType(inputType); } - private static JsonTypeInfo GetTypeInfo(JsonSerializerContext context, Type type) + [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)] + private static JsonTypeInfo GetTypeInfo(JsonSerializerOptions? options) + => (JsonTypeInfo)GetTypeInfo(options, typeof(T)); + + private static JsonTypeInfo GetTypeInfo(JsonSerializerContext context, Type inputType) { Debug.Assert(context != null); - Debug.Assert(type != null); + Debug.Assert(inputType != null); - JsonTypeInfo? info = context.GetTypeInfo(type); + JsonTypeInfo? info = context.GetTypeInfo(inputType); if (info is null) { - ThrowHelper.ThrowInvalidOperationException_NoMetadataForType(type, context); + ThrowHelper.ThrowInvalidOperationException_NoMetadataForType(inputType, context); } + info.EnsureConfigured(); return info; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Document.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Document.cs index f49d96e04c23d..58dd06cf6b0de 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Document.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Document.cs @@ -105,6 +105,7 @@ public static partial class JsonSerializer ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo)); } + jsonTypeInfo.EnsureConfigured(); return ReadDocument(document, jsonTypeInfo); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Element.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Element.cs index f9cd108c47dcc..debacec0ec8b3 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Element.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Element.cs @@ -85,6 +85,7 @@ public static partial class JsonSerializer ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo)); } + jsonTypeInfo.EnsureConfigured(); return ReadUsingMetadata(element, jsonTypeInfo); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Helpers.cs index 6a157f9ba3f73..f3cb5b762fa59 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Helpers.cs @@ -9,45 +9,45 @@ namespace System.Text.Json { public static partial class JsonSerializer { - private static TValue? ReadCore(JsonConverter jsonConverter, ref Utf8JsonReader reader, JsonSerializerOptions options, ref ReadStack state) + private static TValue? ReadCore(ref Utf8JsonReader reader, JsonTypeInfo jsonTypeInfo, ref ReadStack state) { - if (jsonConverter is JsonConverter converter) + if (jsonTypeInfo is JsonTypeInfo typedInfo) { // Call the strongly-typed ReadCore that will not box structs. - return converter.ReadCore(ref reader, options, ref state); + return typedInfo.EffectiveConverter.ReadCore(ref reader, typedInfo.Options, ref state); } - // The non-generic API was called or we have a polymorphic case where TValue is not equal to the T in JsonConverter. - object? value = jsonConverter.ReadCoreAsObject(ref reader, options, ref state); - Debug.Assert(value == null || value is TValue); + // The non-generic API was called. + object? value = jsonTypeInfo.Converter.ReadCoreAsObject(ref reader, jsonTypeInfo.Options, ref state); + Debug.Assert(value is null or TValue); return (TValue?)value; } private static TValue? ReadFromSpan(ReadOnlySpan utf8Json, JsonTypeInfo jsonTypeInfo, int? actualByteCount = null) { + Debug.Assert(jsonTypeInfo.IsConfigured); + JsonSerializerOptions options = jsonTypeInfo.Options; var readerState = new JsonReaderState(options.GetReaderOptions()); var reader = new Utf8JsonReader(utf8Json, isFinalBlock: true, readerState); ReadStack state = default; - jsonTypeInfo.EnsureConfigured(); state.Initialize(jsonTypeInfo); TValue? value; - JsonConverter jsonConverter = jsonTypeInfo.Converter; // For performance, the code below is a lifted ReadCore() above. - if (jsonConverter is JsonConverter converter) + if (jsonTypeInfo is JsonTypeInfo typedInfo) { // Call the strongly-typed ReadCore that will not box structs. - value = converter.ReadCore(ref reader, options, ref state); + value = typedInfo.EffectiveConverter.ReadCore(ref reader, options, ref state); } else { - // The non-generic API was called or we have a polymorphic case where TValue is not equal to the T in JsonConverter. - object? objValue = jsonConverter.ReadCoreAsObject(ref reader, options, ref state); - Debug.Assert(objValue == null || objValue is TValue); + // The non-generic API was called. + object? objValue = jsonTypeInfo.Converter.ReadCoreAsObject(ref reader, options, ref state); + Debug.Assert(objValue is null or TValue); value = (TValue?)objValue; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Node.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Node.cs index 08f770349dac8..21a367264f54b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Node.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Node.cs @@ -84,6 +84,7 @@ public static partial class JsonSerializer ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo)); } + jsonTypeInfo.EnsureConfigured(); return ReadNode(node, jsonTypeInfo); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Span.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Span.cs index 8708f4a92debd..2e0f50c260957 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Span.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Span.cs @@ -88,6 +88,7 @@ public static partial class JsonSerializer ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo)); } + jsonTypeInfo.EnsureConfigured(); return ReadFromSpan(utf8Json, jsonTypeInfo); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs index ae344a1831d3a..833b391752db7 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs @@ -7,6 +7,7 @@ using System.IO; using System.Runtime.CompilerServices; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Converters; using System.Text.Json.Serialization.Metadata; using System.Threading; using System.Threading.Tasks; @@ -51,7 +52,7 @@ public static partial class JsonSerializer } JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, typeof(TValue)); - return ReadAllAsync(utf8Json, jsonTypeInfo, cancellationToken); + return ReadFromStreamAsync(utf8Json, jsonTypeInfo, cancellationToken); } /// @@ -85,7 +86,8 @@ public static partial class JsonSerializer ThrowHelper.ThrowArgumentNullException(nameof(utf8Json)); } - return ReadAllUsingOptions(utf8Json, typeof(TValue), options); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, typeof(TValue)); + return ReadFromStream(utf8Json, jsonTypeInfo); } /// @@ -129,7 +131,7 @@ public static partial class JsonSerializer } JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, returnType); - return ReadAllAsync(utf8Json, jsonTypeInfo, cancellationToken); + return ReadFromStreamAsync(utf8Json, jsonTypeInfo, cancellationToken); } /// @@ -168,7 +170,8 @@ public static partial class JsonSerializer ThrowHelper.ThrowArgumentNullException(nameof(returnType)); } - return ReadAllUsingOptions(utf8Json, returnType, options); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, returnType); + return ReadFromStream(utf8Json, jsonTypeInfo); } /// @@ -208,7 +211,8 @@ public static partial class JsonSerializer ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo)); } - return ReadAllAsync(utf8Json, jsonTypeInfo, cancellationToken); + jsonTypeInfo.EnsureConfigured(); + return ReadFromStreamAsync(utf8Json, jsonTypeInfo, cancellationToken); } /// @@ -244,7 +248,8 @@ public static partial class JsonSerializer ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo)); } - return ReadAll(utf8Json, jsonTypeInfo); + jsonTypeInfo.EnsureConfigured(); + return ReadFromStream(utf8Json, jsonTypeInfo); } /// @@ -293,7 +298,8 @@ public static partial class JsonSerializer ThrowHelper.ThrowArgumentNullException(nameof(context)); } - return ReadAllAsync(utf8Json, GetTypeInfo(context, returnType), cancellationToken); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, returnType); + return ReadFromStreamAsync(utf8Json, jsonTypeInfo, cancellationToken); } /// @@ -338,7 +344,8 @@ public static partial class JsonSerializer ThrowHelper.ThrowArgumentNullException(nameof(context)); } - return ReadAll(utf8Json, GetTypeInfo(context, returnType)); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, returnType); + return ReadFromStream(utf8Json, jsonTypeInfo); } /// @@ -403,7 +410,7 @@ public static partial class JsonSerializer private static JsonTypeInfo> CreateQueueTypeInfo(JsonTypeInfo jsonTypeInfo) { - return JsonMetadataServices.CreateQueueInfo, TValue>( + JsonTypeInfo> queueTypeInfo = JsonMetadataServices.CreateQueueInfo, TValue>( options: jsonTypeInfo.Options, collectionInfo: new() { @@ -411,6 +418,9 @@ private static JsonTypeInfo> CreateQueueTypeInfo(JsonTypeI ElementInfo = jsonTypeInfo, NumberHandling = jsonTypeInfo.Options.NumberHandling }); + + queueTypeInfo.EnsureConfigured(); + return queueTypeInfo; } private static async IAsyncEnumerable CreateAsyncEnumerableDeserializer( @@ -418,11 +428,16 @@ private static async IAsyncEnumerable CreateAsyncEnumerableDeserializer< JsonTypeInfo> queueTypeInfo, [EnumeratorCancellation] CancellationToken cancellationToken) { - queueTypeInfo.EnsureConfigured(); + Debug.Assert(queueTypeInfo.IsConfigured); JsonSerializerOptions options = queueTypeInfo.Options; var bufferState = new ReadBufferState(options.DefaultBufferSize); - ReadStack readStack = default; - readStack.Initialize(queueTypeInfo, supportContinuation: true); + ReadStack readStack = new ReadStack + { + SupportContinuation = true + }; + + readStack.Initialize(queueTypeInfo); + var jsonReaderState = new JsonReaderState(options.GetReaderOptions()); try @@ -434,8 +449,7 @@ private static async IAsyncEnumerable CreateAsyncEnumerableDeserializer< ref bufferState, ref jsonReaderState, ref readStack, - queueTypeInfo.Converter, - options); + queueTypeInfo); if (readStack.Current.ReturnValue is Queue queue) { @@ -453,17 +467,19 @@ private static async IAsyncEnumerable CreateAsyncEnumerableDeserializer< } } - internal static async ValueTask ReadAllAsync( + internal static async ValueTask ReadFromStreamAsync( Stream utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken) { + Debug.Assert(jsonTypeInfo.IsConfigured); JsonSerializerOptions options = jsonTypeInfo.Options; var bufferState = new ReadBufferState(options.DefaultBufferSize); - ReadStack readStack = default; - jsonTypeInfo.EnsureConfigured(); - readStack.Initialize(jsonTypeInfo, supportContinuation: true); - JsonConverter converter = readStack.Current.JsonPropertyInfo!.EffectiveConverter; + ReadStack readStack = new ReadStack + { + SupportContinuation = true + }; + readStack.Initialize(jsonTypeInfo); var jsonReaderState = new JsonReaderState(options.GetReaderOptions()); try @@ -471,11 +487,11 @@ private static async IAsyncEnumerable CreateAsyncEnumerableDeserializer< while (true) { bufferState = await bufferState.ReadFromStreamAsync(utf8Json, cancellationToken).ConfigureAwait(false); - TValue value = ContinueDeserialize(ref bufferState, ref jsonReaderState, ref readStack, converter, options); + TValue? value = ContinueDeserialize(ref bufferState, ref jsonReaderState, ref readStack, jsonTypeInfo); if (bufferState.IsFinalBlock) { - return value!; + return value; } } } @@ -485,16 +501,18 @@ private static async IAsyncEnumerable CreateAsyncEnumerableDeserializer< } } - internal static TValue? ReadAll( + internal static TValue? ReadFromStream( Stream utf8Json, JsonTypeInfo jsonTypeInfo) { + Debug.Assert(jsonTypeInfo.IsConfigured); JsonSerializerOptions options = jsonTypeInfo.Options; var bufferState = new ReadBufferState(options.DefaultBufferSize); - ReadStack readStack = default; - jsonTypeInfo.EnsureConfigured(); - readStack.Initialize(jsonTypeInfo, supportContinuation: true); - JsonConverter converter = readStack.Current.JsonPropertyInfo!.EffectiveConverter; + ReadStack readStack = new ReadStack + { + SupportContinuation = true + }; + readStack.Initialize(jsonTypeInfo); var jsonReaderState = new JsonReaderState(options.GetReaderOptions()); try @@ -502,11 +520,11 @@ private static async IAsyncEnumerable CreateAsyncEnumerableDeserializer< while (true) { bufferState.ReadFromStream(utf8Json); - TValue value = ContinueDeserialize(ref bufferState, ref jsonReaderState, ref readStack, converter, options); + TValue? value = ContinueDeserialize(ref bufferState, ref jsonReaderState, ref readStack, jsonTypeInfo); if (bufferState.IsFinalBlock) { - return value!; + return value; } } } @@ -516,59 +534,26 @@ private static async IAsyncEnumerable CreateAsyncEnumerableDeserializer< } } - internal static TValue ContinueDeserialize( + internal static TValue? ContinueDeserialize( ref ReadBufferState bufferState, ref JsonReaderState jsonReaderState, ref ReadStack readStack, - JsonConverter converter, - JsonSerializerOptions options) - { - // Process the data available - TValue value = ReadCore( - ref jsonReaderState, - bufferState.IsFinalBlock, - bufferState.Bytes, - options, - ref readStack, - converter); - - Debug.Assert(readStack.BytesConsumed <= bufferState.Bytes.Length); - bufferState.AdvanceBuffer((int)readStack.BytesConsumed); - - return value; - } - - [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)] - [RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)] - private static TValue? ReadAllUsingOptions( - Stream utf8Json, - Type returnType, - JsonSerializerOptions? options) - { - JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, returnType); - return ReadAll(utf8Json, jsonTypeInfo); - } - - private static TValue ReadCore( - ref JsonReaderState readerState, - bool isFinalBlock, - ReadOnlySpan buffer, - JsonSerializerOptions options, - ref ReadStack state, - JsonConverter converterBase) + JsonTypeInfo jsonTypeInfo) { - var reader = new Utf8JsonReader(buffer, isFinalBlock, readerState); + var reader = new Utf8JsonReader(bufferState.Bytes, bufferState.IsFinalBlock, jsonReaderState); // If we haven't read in the entire stream's payload we'll need to signify that we want // to enable read ahead behaviors to ensure we have complete json objects and arrays // ({}, []) when needed. (Notably to successfully parse JsonElement via JsonDocument // to assign to object and JsonElement properties in the constructed .NET object.) - state.ReadAhead = !isFinalBlock; - state.BytesConsumed = 0; + readStack.ReadAhead = !bufferState.IsFinalBlock; + readStack.BytesConsumed = 0; - TValue? value = ReadCore(converterBase, ref reader, options, ref state); - readerState = reader.CurrentState; - return value!; + TValue? value = ReadCore(ref reader, jsonTypeInfo, ref readStack); + Debug.Assert(readStack.BytesConsumed <= bufferState.Bytes.Length); + bufferState.AdvanceBuffer((int)readStack.BytesConsumed); + jsonReaderState = reader.CurrentState; + return value; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs index e9bb1f1069d41..1a307f31936c0 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; @@ -216,6 +217,7 @@ public static partial class JsonSerializer ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo)); } + jsonTypeInfo.EnsureConfigured(); return ReadFromSpan(json.AsSpan(), jsonTypeInfo); } @@ -257,6 +259,7 @@ public static partial class JsonSerializer ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo)); } + jsonTypeInfo.EnsureConfigured(); return ReadFromSpan(json, jsonTypeInfo); } @@ -366,7 +369,7 @@ public static partial class JsonSerializer private static TValue? ReadFromSpan(ReadOnlySpan json, JsonTypeInfo jsonTypeInfo) { - jsonTypeInfo.EnsureConfigured(); + Debug.Assert(jsonTypeInfo.IsConfigured); byte[]? tempArray = null; // For performance, avoid obtaining actual byte count unless memory usage is higher than the threshold. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Utf8JsonReader.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Utf8JsonReader.cs index 5fb3adb99ba6b..b29d7d562b02b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Utf8JsonReader.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Utf8JsonReader.cs @@ -170,6 +170,7 @@ public static partial class JsonSerializer ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo)); } + jsonTypeInfo.EnsureConfigured(); return Read(ref reader, jsonTypeInfo); } @@ -238,8 +239,8 @@ public static partial class JsonSerializer private static TValue? Read(ref Utf8JsonReader reader, JsonTypeInfo jsonTypeInfo) { + Debug.Assert(jsonTypeInfo.IsConfigured); ReadStack state = default; - jsonTypeInfo.EnsureConfigured(); state.Initialize(jsonTypeInfo); JsonReaderState readerState = reader.CurrentState; @@ -422,8 +423,7 @@ public static partial class JsonSerializer var newReader = new Utf8JsonReader(rentedSpan, originalReaderOptions); - JsonConverter jsonConverter = state.Current.JsonPropertyInfo!.EffectiveConverter; - TValue? value = ReadCore(jsonConverter, ref newReader, jsonTypeInfo.Options, ref state); + TValue? value = ReadCore(ref newReader, jsonTypeInfo, ref state); // The reader should have thrown if we have remaining bytes. Debug.Assert(newReader.BytesConsumed == length); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs index dda1088e7acf9..49aa791c8dde9 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; @@ -25,9 +26,8 @@ public static byte[] SerializeToUtf8Bytes( TValue value, JsonSerializerOptions? options = null) { - Type runtimeType = GetRuntimeType(value); - JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType); - return WriteBytesUsingSerializer(value, jsonTypeInfo); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(options); + return WriteBytes(value, jsonTypeInfo); } /// @@ -54,9 +54,9 @@ public static byte[] SerializeToUtf8Bytes( Type inputType, JsonSerializerOptions? options = null) { - Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType); - JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType); - return WriteBytesUsingSerializer(value, jsonTypeInfo); + ValidateInputType(value, inputType); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, inputType); + return WriteBytesAsObject(value, jsonTypeInfo); } /// @@ -79,7 +79,8 @@ public static byte[] SerializeToUtf8Bytes(TValue value, JsonTypeInfo @@ -110,34 +111,33 @@ public static byte[] SerializeToUtf8Bytes(object? value, Type inputType, JsonSer ThrowHelper.ThrowArgumentNullException(nameof(context)); } - Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType); - JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, runtimeType); - return WriteBytesUsingGeneratedSerializer(value!, jsonTypeInfo); + ValidateInputType(value, inputType); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, inputType); + return WriteBytesAsObject(value, jsonTypeInfo); } - private static byte[] WriteBytesUsingGeneratedSerializer(in TValue value, JsonTypeInfo jsonTypeInfo) + private static byte[] WriteBytes(in TValue value, JsonTypeInfo jsonTypeInfo) { + Debug.Assert(jsonTypeInfo.IsConfigured); + JsonSerializerOptions options = jsonTypeInfo.Options; using var output = new PooledByteBufferWriter(options.DefaultBufferSize); - using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions())) - { - WriteUsingGeneratedSerializer(writer, value, jsonTypeInfo); - } + using var writer = new Utf8JsonWriter(output, options.GetWriterOptions()); + WriteCore(writer, value, jsonTypeInfo); return output.WrittenMemory.ToArray(); } - private static byte[] WriteBytesUsingSerializer(in TValue value, JsonTypeInfo jsonTypeInfo) + private static byte[] WriteBytesAsObject(object? value, JsonTypeInfo jsonTypeInfo) { + Debug.Assert(jsonTypeInfo.IsConfigured); + JsonSerializerOptions options = jsonTypeInfo.Options; using var output = new PooledByteBufferWriter(options.DefaultBufferSize); - using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions())) - { - WriteUsingSerializer(writer, value, jsonTypeInfo); - } - + using var writer = new Utf8JsonWriter(output, options.GetWriterOptions()); + WriteCoreAsObject(writer, value, jsonTypeInfo); return output.WrittenMemory.ToArray(); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Document.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Document.cs index 731dd0b46cb4a..13ff63a2bc536 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Document.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Document.cs @@ -25,9 +25,8 @@ public static partial class JsonSerializer [RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)] public static JsonDocument SerializeToDocument(TValue value, JsonSerializerOptions? options = null) { - Type runtimeType = GetRuntimeType(value); - JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType); - return WriteDocumentUsingSerializer(value, jsonTypeInfo); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(options); + return WriteDocument(value, jsonTypeInfo); } /// @@ -51,9 +50,9 @@ public static JsonDocument SerializeToDocument(TValue value, JsonSeriali [RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)] public static JsonDocument SerializeToDocument(object? value, Type inputType, JsonSerializerOptions? options = null) { - Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType); - JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType); - return WriteDocumentUsingSerializer(value, jsonTypeInfo); + ValidateInputType(value, inputType); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, inputType); + return WriteDocumentAsObject(value, jsonTypeInfo); } /// @@ -77,7 +76,8 @@ public static JsonDocument SerializeToDocument(TValue value, JsonTypeInf ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo)); } - return WriteDocumentUsingGeneratedSerializer(value, jsonTypeInfo); + jsonTypeInfo.EnsureConfigured(); + return WriteDocument(value, jsonTypeInfo); } /// @@ -105,39 +105,35 @@ public static JsonDocument SerializeToDocument(object? value, Type inputType, Js ThrowHelper.ThrowArgumentNullException(nameof(context)); } - Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType); - return WriteDocumentUsingGeneratedSerializer(value, GetTypeInfo(context, runtimeType)); + ValidateInputType(value, inputType); + return WriteDocumentAsObject(value, GetTypeInfo(context, inputType)); } - private static JsonDocument WriteDocumentUsingGeneratedSerializer(in TValue value, JsonTypeInfo jsonTypeInfo) + private static JsonDocument WriteDocument(in TValue value, JsonTypeInfo jsonTypeInfo) { + Debug.Assert(jsonTypeInfo.IsConfigured); JsonSerializerOptions options = jsonTypeInfo.Options; - Debug.Assert(options != null); // For performance, share the same buffer across serialization and deserialization. // The PooledByteBufferWriter is cleared and returned when JsonDocument.Dispose() is called. PooledByteBufferWriter output = new(options.DefaultBufferSize); - using (Utf8JsonWriter writer = new(output, options.GetWriterOptions())) - { - WriteUsingGeneratedSerializer(writer, value, jsonTypeInfo); - } + using Utf8JsonWriter writer = new(output, options.GetWriterOptions()); + WriteCore(writer, value, jsonTypeInfo); return JsonDocument.ParseRented(output, options.GetDocumentOptions()); } - private static JsonDocument WriteDocumentUsingSerializer(in TValue value, JsonTypeInfo jsonTypeInfo) + private static JsonDocument WriteDocumentAsObject(object? value, JsonTypeInfo jsonTypeInfo) { + Debug.Assert(jsonTypeInfo.IsConfigured); JsonSerializerOptions options = jsonTypeInfo.Options; - Debug.Assert(options != null); // For performance, share the same buffer across serialization and deserialization. // The PooledByteBufferWriter is cleared and returned when JsonDocument.Dispose() is called. PooledByteBufferWriter output = new(options.DefaultBufferSize); - using (Utf8JsonWriter writer = new(output, options.GetWriterOptions())) - { - WriteUsingSerializer(writer, value, jsonTypeInfo); - } + using Utf8JsonWriter writer = new(output, options.GetWriterOptions()); + WriteCoreAsObject(writer, value, jsonTypeInfo); return JsonDocument.ParseRented(output, options.GetDocumentOptions()); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Element.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Element.cs index 3343f544a1c11..ba35d4c4f9115 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Element.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Element.cs @@ -25,9 +25,8 @@ public static partial class JsonSerializer [RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)] public static JsonElement SerializeToElement(TValue value, JsonSerializerOptions? options = null) { - Type runtimeType = GetRuntimeType(value); - JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType); - return WriteElementUsingSerializer(value, jsonTypeInfo); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(options); + return WriteElement(value, jsonTypeInfo); } /// @@ -51,9 +50,9 @@ public static JsonElement SerializeToElement(TValue value, JsonSerialize [RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)] public static JsonElement SerializeToElement(object? value, Type inputType, JsonSerializerOptions? options = null) { - Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType); - JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType); - return WriteElementUsingSerializer(value, jsonTypeInfo); + ValidateInputType(value, inputType); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, inputType); + return WriteElementAsObject(value, jsonTypeInfo); } /// @@ -77,7 +76,8 @@ public static JsonElement SerializeToElement(TValue value, JsonTypeInfo< ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo)); } - return WriteElementUsingGeneratedSerializer(value, jsonTypeInfo); + jsonTypeInfo.EnsureConfigured(); + return WriteElement(value, jsonTypeInfo); } /// @@ -105,38 +105,34 @@ public static JsonElement SerializeToElement(object? value, Type inputType, Json ThrowHelper.ThrowArgumentNullException(nameof(context)); } - Type type = GetRuntimeTypeAndValidateInputType(value, inputType); - JsonTypeInfo typeInfo = GetTypeInfo(context, type); - return WriteElementUsingGeneratedSerializer(value, typeInfo); + ValidateInputType(value, inputType); + JsonTypeInfo typeInfo = GetTypeInfo(context, inputType); + return WriteElementAsObject(value, typeInfo); } - private static JsonElement WriteElementUsingGeneratedSerializer(in TValue value, JsonTypeInfo jsonTypeInfo) + private static JsonElement WriteElement(in TValue value, JsonTypeInfo jsonTypeInfo) { + Debug.Assert(jsonTypeInfo.IsConfigured); JsonSerializerOptions options = jsonTypeInfo.Options; - Debug.Assert(options != null); // For performance, share the same buffer across serialization and deserialization. using var output = new PooledByteBufferWriter(options.DefaultBufferSize); - using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions())) - { - WriteUsingGeneratedSerializer(writer, value, jsonTypeInfo); - } + using var writer = new Utf8JsonWriter(output, options.GetWriterOptions()); + WriteCore(writer, value, jsonTypeInfo); return JsonElement.ParseValue(output.WrittenMemory.Span, options.GetDocumentOptions()); } - private static JsonElement WriteElementUsingSerializer(in TValue value, JsonTypeInfo jsonTypeInfo) + private static JsonElement WriteElementAsObject(object? value, JsonTypeInfo jsonTypeInfo) { JsonSerializerOptions options = jsonTypeInfo.Options; Debug.Assert(options != null); // For performance, share the same buffer across serialization and deserialization. using var output = new PooledByteBufferWriter(options.DefaultBufferSize); - using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions())) - { - WriteUsingSerializer(writer, value, jsonTypeInfo); - } + using var writer = new Utf8JsonWriter(output, options.GetWriterOptions()); + WriteCoreAsObject(writer, value, jsonTypeInfo); return JsonElement.ParseValue(output.WrittenMemory.Span, options.GetDocumentOptions()); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs index d80d5fbc34ef2..1f688d480fabd 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs @@ -2,98 +2,112 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Converters; using System.Text.Json.Serialization.Metadata; namespace System.Text.Json { public static partial class JsonSerializer { - private static bool WriteCore( - JsonConverter jsonConverter, + /// + /// Sync, strongly typed root value serialization helper. + /// + private static void WriteCore( Utf8JsonWriter writer, in TValue value, - JsonSerializerOptions options, - ref WriteStack state) + JsonTypeInfo jsonTypeInfo) { - Debug.Assert(writer != null); + if (jsonTypeInfo.CanUseSerializeHandler) + { + // Short-circuit calls into SerializeHandler, if supported. + // Even though this is already handled by JsonMetadataServicesConverter, + // we avoid instantiating a WriteStack and a couple of additional virtual calls. - bool success; + Debug.Assert(jsonTypeInfo.SerializeHandler != null); + Debug.Assert(jsonTypeInfo.Options.SerializerContext?.CanUseSerializationLogic == true); + Debug.Assert(jsonTypeInfo.Converter is JsonMetadataServicesConverter); - if (jsonConverter is JsonConverter converter) - { - // Call the strongly-typed WriteCore that will not box structs. - success = converter.WriteCore(writer, value, options, ref state); + jsonTypeInfo.SerializeHandler(writer, value); } else { - // The non-generic API was called or we have a polymorphic case where TValue is not equal to the T in JsonConverter. - success = jsonConverter.WriteCoreAsObject(writer, value, options, ref state); + WriteStack state = default; + JsonTypeInfo polymorphicTypeInfo = ResolvePolymorphicTypeInfo(value, jsonTypeInfo, out state.IsPolymorphicRootValue); + state.Initialize(polymorphicTypeInfo); + + bool success = + state.IsPolymorphicRootValue + ? polymorphicTypeInfo.Converter.WriteCoreAsObject(writer, value, jsonTypeInfo.Options, ref state) + : jsonTypeInfo.EffectiveConverter.WriteCore(writer, value, jsonTypeInfo.Options, ref state); + + Debug.Assert(success); } writer.Flush(); - return success; } - private static void WriteUsingGeneratedSerializer(Utf8JsonWriter writer, in TValue value, JsonTypeInfo jsonTypeInfo) + /// + /// Sync, untyped root value serialization helper. + /// + private static void WriteCoreAsObject( + Utf8JsonWriter writer, + object? value, + JsonTypeInfo jsonTypeInfo) { - Debug.Assert(writer != null); + WriteStack state = default; + JsonTypeInfo polymorphicTypeInfo = ResolvePolymorphicTypeInfo(value, jsonTypeInfo, out state.IsPolymorphicRootValue); + state.Initialize(polymorphicTypeInfo); - if (jsonTypeInfo.HasSerializeHandler && - jsonTypeInfo is JsonTypeInfo typedInfo && - typedInfo.Options.SerializerContext?.CanUseSerializationLogic == true) - { - Debug.Assert(typedInfo.SerializeHandler != null); - typedInfo.SerializeHandler(writer, value); - writer.Flush(); - } - else - { - WriteUsingSerializer(writer, value, jsonTypeInfo); - } + bool success = polymorphicTypeInfo.Converter.WriteCoreAsObject(writer, value, jsonTypeInfo.Options, ref state); + Debug.Assert(success); + writer.Flush(); } - private static void WriteUsingSerializer(Utf8JsonWriter writer, in TValue value, JsonTypeInfo jsonTypeInfo) + /// + /// Streaming root-level serialization helper. + /// + private static bool WriteCore(Utf8JsonWriter writer, in TValue value, JsonTypeInfo jsonTypeInfo, ref WriteStack state) { - Debug.Assert(writer != null); + Debug.Assert(state.SupportContinuation); - // TODO unify method with WriteUsingGeneratedSerializer - - WriteStack state = default; - jsonTypeInfo.EnsureConfigured(); - state.Initialize(jsonTypeInfo, supportContinuation: false, supportAsync: false); - - JsonConverter converter = jsonTypeInfo.Converter; - Debug.Assert(converter != null); - Debug.Assert(jsonTypeInfo.Options != null); - - // For performance, the code below is a lifted WriteCore() above. - if (converter is JsonConverter typedConverter) + bool isFinalBlock; + if (jsonTypeInfo is JsonTypeInfo typedInfo) { - // Call the strongly-typed WriteCore that will not box structs. - typedConverter.WriteCore(writer, value, jsonTypeInfo.Options, ref state); + isFinalBlock = typedInfo.EffectiveConverter.WriteCore(writer, value, jsonTypeInfo.Options, ref state); } else { - // The non-generic API was called or we have a polymorphic case where TValue is not equal to the T in JsonConverter. - converter.WriteCoreAsObject(writer, value, jsonTypeInfo.Options, ref state); + // The non-generic API was called. + isFinalBlock = jsonTypeInfo.Converter.WriteCoreAsObject(writer, value, jsonTypeInfo.Options, ref state); } writer.Flush(); + return isFinalBlock; } - private static Type GetRuntimeType(in TValue value) + private static JsonTypeInfo ResolvePolymorphicTypeInfo(in TValue value, JsonTypeInfo jsonTypeInfo, out bool isPolymorphicType) { - Type type = typeof(TValue); - if (type == JsonTypeInfo.ObjectType && value is not null) + if ( +#if NETCOREAPP + !typeof(TValue).IsValueType && +#endif + jsonTypeInfo.Converter.CanBePolymorphic && value is not null) { - type = value.GetType(); + Debug.Assert(typeof(TValue) == typeof(object)); + + Type runtimeType = value.GetType(); + if (runtimeType != jsonTypeInfo.Type) + { + isPolymorphicType = true; + return jsonTypeInfo.Options.GetTypeInfoForRootType(runtimeType); + } } - return type; + isPolymorphicType = false; + return jsonTypeInfo; } - private static Type GetRuntimeTypeAndValidateInputType(object? value, Type inputType) + private static void ValidateInputType(object? value, Type inputType) { if (inputType is null) { @@ -107,14 +121,7 @@ private static Type GetRuntimeTypeAndValidateInputType(object? value, Type input { ThrowHelper.ThrowArgumentException_DeserializeWrongType(inputType, value); } - - if (inputType == JsonTypeInfo.ObjectType) - { - return runtimeType; - } } - - return inputType; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Node.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Node.cs index 5f24f4f600a0c..379b80409a53c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Node.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Node.cs @@ -26,9 +26,8 @@ public static partial class JsonSerializer [RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)] public static JsonNode? SerializeToNode(TValue value, JsonSerializerOptions? options = null) { - Type runtimeType = GetRuntimeType(value); - JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType); - return WriteNodeUsingSerializer(value, jsonTypeInfo); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(options); + return WriteNode(value, jsonTypeInfo); } /// @@ -52,9 +51,9 @@ public static partial class JsonSerializer [RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)] public static JsonNode? SerializeToNode(object? value, Type inputType, JsonSerializerOptions? options = null) { - Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType); - JsonTypeInfo typeInfo = GetTypeInfo(options, runtimeType); - return WriteNodeUsingSerializer(value, typeInfo); + ValidateInputType(value, inputType); + JsonTypeInfo typeInfo = GetTypeInfo(options, inputType); + return WriteNodeAsObject(value, typeInfo); } /// @@ -78,7 +77,8 @@ public static partial class JsonSerializer ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo)); } - return WriteNodeUsingGeneratedSerializer(value, jsonTypeInfo); + jsonTypeInfo.EnsureConfigured(); + return WriteNode(value, jsonTypeInfo); } /// @@ -106,38 +106,34 @@ public static partial class JsonSerializer ThrowHelper.ThrowArgumentNullException(nameof(context)); } - Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType); - JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, runtimeType); - return WriteNodeUsingGeneratedSerializer(value, jsonTypeInfo); + ValidateInputType(value, inputType); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, inputType); + return WriteNodeAsObject(value, jsonTypeInfo); } - private static JsonNode? WriteNodeUsingGeneratedSerializer(in TValue value, JsonTypeInfo jsonTypeInfo) + private static JsonNode? WriteNode(in TValue value, JsonTypeInfo jsonTypeInfo) { + Debug.Assert(jsonTypeInfo.IsConfigured); JsonSerializerOptions options = jsonTypeInfo.Options; - Debug.Assert(options != null); // For performance, share the same buffer across serialization and deserialization. using var output = new PooledByteBufferWriter(options.DefaultBufferSize); - using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions())) - { - WriteUsingGeneratedSerializer(writer, value, jsonTypeInfo); - } + using var writer = new Utf8JsonWriter(output, options.GetWriterOptions()); + WriteCore(writer, value, jsonTypeInfo); return JsonNode.Parse(output.WrittenMemory.Span, options.GetNodeOptions(), options.GetDocumentOptions()); } - private static JsonNode? WriteNodeUsingSerializer(in TValue value, JsonTypeInfo jsonTypeInfo) + private static JsonNode? WriteNodeAsObject(object? value, JsonTypeInfo jsonTypeInfo) { + Debug.Assert(jsonTypeInfo.IsConfigured); JsonSerializerOptions options = jsonTypeInfo.Options; - Debug.Assert(options != null); // For performance, share the same buffer across serialization and deserialization. using var output = new PooledByteBufferWriter(options.DefaultBufferSize); - using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions())) - { - WriteUsingSerializer(writer, value, jsonTypeInfo); - } + using var writer = new Utf8JsonWriter(output, options.GetWriterOptions()); + WriteCoreAsObject(writer, value, jsonTypeInfo); return JsonNode.Parse(output.WrittenMemory.Span, options.GetNodeOptions(), options.GetDocumentOptions()); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs index 9fb7c5c839421..d6c4734bfc5d0 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs @@ -50,9 +50,8 @@ public static Task SerializeAsync( ThrowHelper.ThrowArgumentNullException(nameof(utf8Json)); } - Type runtimeType = GetRuntimeType(value); - JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType); - return WriteStreamAsync(utf8Json, value!, jsonTypeInfo, cancellationToken); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, typeof(TValue)); + return WriteStreamAsync(utf8Json, value, jsonTypeInfo, cancellationToken); } /// @@ -81,9 +80,8 @@ public static void Serialize( ThrowHelper.ThrowArgumentNullException(nameof(utf8Json)); } - Type runtimeType = GetRuntimeType(value); - JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType); - WriteStream(utf8Json, value!, jsonTypeInfo); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, typeof(TValue)); + WriteStream(utf8Json, value, jsonTypeInfo); } /// @@ -119,9 +117,9 @@ public static Task SerializeAsync( ThrowHelper.ThrowArgumentNullException(nameof(utf8Json)); } - Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType); - JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType); - return WriteStreamAsync(utf8Json, value!, jsonTypeInfo, cancellationToken); + ValidateInputType(value, inputType); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, inputType); + return WriteStreamAsync(utf8Json, value, jsonTypeInfo, cancellationToken); } /// @@ -154,9 +152,9 @@ public static void Serialize( ThrowHelper.ThrowArgumentNullException(nameof(utf8Json)); } - Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType); - JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType); - WriteStream(utf8Json, value!, jsonTypeInfo); + ValidateInputType(value, inputType); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, inputType); + WriteStream(utf8Json, value, jsonTypeInfo); } /// @@ -259,11 +257,11 @@ public static Task SerializeAsync( ThrowHelper.ThrowArgumentNullException(nameof(context)); } - Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType); + ValidateInputType(value, inputType); return WriteStreamAsync( utf8Json, - value!, - GetTypeInfo(context, runtimeType), + value, + GetTypeInfo(context, inputType), cancellationToken); } @@ -299,8 +297,8 @@ public static void Serialize( ThrowHelper.ThrowArgumentNullException(nameof(context)); } - Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType); - WriteStream(utf8Json, value!, GetTypeInfo(context, runtimeType)); + ValidateInputType(value, inputType); + WriteStream(utf8Json, value, GetTypeInfo(context, inputType)); } private static async Task WriteStreamAsync( @@ -309,15 +307,22 @@ private static async Task WriteStreamAsync( JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken) { + jsonTypeInfo.EnsureConfigured(); JsonSerializerOptions options = jsonTypeInfo.Options; JsonWriterOptions writerOptions = options.GetWriterOptions(); using (var bufferWriter = new PooledByteBufferWriter(options.DefaultBufferSize)) using (var writer = new Utf8JsonWriter(bufferWriter, writerOptions)) { - WriteStack state = new WriteStack { CancellationToken = cancellationToken }; - jsonTypeInfo.EnsureConfigured(); - JsonConverter converter = state.Initialize(jsonTypeInfo, supportContinuation: true, supportAsync: true); + WriteStack state = new WriteStack + { + CancellationToken = cancellationToken, + SupportContinuation = true, + SupportAsync = true, + }; + + jsonTypeInfo = ResolvePolymorphicTypeInfo(value, jsonTypeInfo, out state.IsPolymorphicRootValue); + state.Initialize(jsonTypeInfo); bool isFinalBlock; @@ -329,7 +334,7 @@ private static async Task WriteStreamAsync( try { - isFinalBlock = WriteCore(converter, writer, value, options, ref state); + isFinalBlock = WriteCore(writer, value, jsonTypeInfo, ref state); if (state.SuppressFlush) { @@ -383,15 +388,20 @@ private static void WriteStream( in TValue value, JsonTypeInfo jsonTypeInfo) { + jsonTypeInfo.EnsureConfigured(); JsonSerializerOptions options = jsonTypeInfo.Options; JsonWriterOptions writerOptions = options.GetWriterOptions(); using (var bufferWriter = new PooledByteBufferWriter(options.DefaultBufferSize)) using (var writer = new Utf8JsonWriter(bufferWriter, writerOptions)) { - WriteStack state = default; - jsonTypeInfo.EnsureConfigured(); - JsonConverter converter = state.Initialize(jsonTypeInfo, supportContinuation: true, supportAsync: false); + WriteStack state = new WriteStack + { + SupportContinuation = true + }; + + jsonTypeInfo = ResolvePolymorphicTypeInfo(value, jsonTypeInfo, out state.IsPolymorphicRootValue); + state.Initialize(jsonTypeInfo); bool isFinalBlock; @@ -399,7 +409,7 @@ private static void WriteStream( { state.FlushThreshold = (int)(bufferWriter.Capacity * FlushThreshold); - isFinalBlock = WriteCore(converter, writer, value, options, ref state); + isFinalBlock = WriteCore(writer, value, jsonTypeInfo, ref state); bufferWriter.WriteToStream(utf8Json); bufferWriter.Clear(); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs index 674a3063956c7..451565a3d13ce 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; @@ -28,9 +29,8 @@ public static partial class JsonSerializer [RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)] public static string Serialize(TValue value, JsonSerializerOptions? options = null) { - Type runtimeType = GetRuntimeType(value); - JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType); - return WriteStringUsingSerializer(value, jsonTypeInfo); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(options); + return WriteString(value, jsonTypeInfo); } /// @@ -61,9 +61,9 @@ public static string Serialize( Type inputType, JsonSerializerOptions? options = null) { - Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType); - JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType); - return WriteStringUsingSerializer(value, jsonTypeInfo); + ValidateInputType(value, inputType); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, inputType); + return WriteStringAsObject(value, jsonTypeInfo); } /// @@ -86,7 +86,13 @@ public static string Serialize( /// public static string Serialize(TValue value, JsonTypeInfo jsonTypeInfo) { - return WriteStringUsingGeneratedSerializer(value, jsonTypeInfo); + if (jsonTypeInfo is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo)); + } + + jsonTypeInfo.EnsureConfigured(); + return WriteString(value, jsonTypeInfo); } /// @@ -118,49 +124,35 @@ public static string Serialize(object? value, Type inputType, JsonSerializerCont ThrowHelper.ThrowArgumentNullException(nameof(context)); } - Type type = GetRuntimeTypeAndValidateInputType(value, inputType); - JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, type); - return WriteStringUsingGeneratedSerializer(value, jsonTypeInfo); + ValidateInputType(value, inputType); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, inputType); + return WriteStringAsObject(value, jsonTypeInfo); } - private static string WriteStringUsingGeneratedSerializer(in TValue value, JsonTypeInfo jsonTypeInfo) + private static string WriteString(in TValue value, JsonTypeInfo jsonTypeInfo) { - if (jsonTypeInfo is null) - { - ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo)); - } + Debug.Assert(jsonTypeInfo.IsConfigured); JsonSerializerOptions options = jsonTypeInfo.Options; - using (var output = new PooledByteBufferWriter(options.DefaultBufferSize)) - { - using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions())) - { - WriteUsingGeneratedSerializer(writer, value, jsonTypeInfo); - } + using var output = new PooledByteBufferWriter(options.DefaultBufferSize); + using var writer = new Utf8JsonWriter(output, options.GetWriterOptions()); - return JsonReaderHelper.TranscodeHelper(output.WrittenMemory.Span); - } + WriteCore(writer, value, jsonTypeInfo); + return JsonReaderHelper.TranscodeHelper(output.WrittenMemory.Span); } - private static string WriteStringUsingSerializer(in TValue value, JsonTypeInfo jsonTypeInfo) + private static string WriteStringAsObject(object? value, JsonTypeInfo jsonTypeInfo) { - if (jsonTypeInfo is null) - { - ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo)); - } + Debug.Assert(jsonTypeInfo.IsConfigured); JsonSerializerOptions options = jsonTypeInfo.Options; - using (var output = new PooledByteBufferWriter(options.DefaultBufferSize)) - { - using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions())) - { - WriteUsingSerializer(writer, value, jsonTypeInfo); - } + using var output = new PooledByteBufferWriter(options.DefaultBufferSize); + using var writer = new Utf8JsonWriter(output, options.GetWriterOptions()); - return JsonReaderHelper.TranscodeHelper(output.WrittenMemory.Span); - } + WriteCoreAsObject(writer, value, jsonTypeInfo); + return JsonReaderHelper.TranscodeHelper(output.WrittenMemory.Span); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Utf8JsonWriter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Utf8JsonWriter.cs index 7f27dcdd50a2e..20ffe8d767529 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Utf8JsonWriter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Utf8JsonWriter.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; @@ -35,9 +36,8 @@ public static void Serialize( ThrowHelper.ThrowArgumentNullException(nameof(writer)); } - Type runtimeType = GetRuntimeType(value); - JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType); - WriteUsingSerializer(writer, value, jsonTypeInfo); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(options); + WriteCore(writer, value, jsonTypeInfo); } /// @@ -70,9 +70,9 @@ public static void Serialize( ThrowHelper.ThrowArgumentNullException(nameof(writer)); } - Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType); - JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType); - WriteUsingSerializer(writer, value, jsonTypeInfo); + ValidateInputType(value, inputType); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, inputType); + WriteCoreAsObject(writer, value, jsonTypeInfo); } /// @@ -100,7 +100,8 @@ public static void Serialize(Utf8JsonWriter writer, TValue value, JsonTy ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo)); } - WriteUsingGeneratedSerializer(writer, value, jsonTypeInfo); + jsonTypeInfo.EnsureConfigured(); + WriteCore(writer, value, jsonTypeInfo); } /// @@ -135,8 +136,9 @@ public static void Serialize(Utf8JsonWriter writer, object? value, Type inputTyp ThrowHelper.ThrowArgumentNullException(nameof(context)); } - Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType); - WriteUsingGeneratedSerializer(writer, value, GetTypeInfo(context, runtimeType)); + ValidateInputType(value, inputType); + JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, inputType); + WriteCoreAsObject(writer, value, jsonTypeInfo); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs index cad93de17744d..f0a7e71dbc965 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs @@ -100,17 +100,29 @@ internal JsonTypeInfo GetTypeInfoForRootType(Type type) if (jsonTypeInfo?.Type != type) { - jsonTypeInfo = GetTypeInfoInternal(type); - _lastTypeInfo = jsonTypeInfo; + _lastTypeInfo = jsonTypeInfo = GetTypeInfoInternal(type); } return jsonTypeInfo; } + // Caches the resolved JsonTypeInfo for faster access during root-level object type serialization. + internal JsonTypeInfo ObjectTypeInfo + { + get + { + Debug.Assert(IsImmutable); + return _objectTypeInfo ??= GetTypeInfoInternal(JsonTypeInfo.ObjectType); + } + } + + private JsonTypeInfo? _objectTypeInfo; + internal void ClearCaches() { _cachingContext?.Clear(); _lastTypeInfo = null; + _objectTypeInfo = null; } private CachingContext? GetCachingContext() diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs index b6898db0868dc..596a9c37bae48 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs @@ -630,8 +630,12 @@ internal void InitializeForReflectionSerializer() DefaultJsonTypeInfoResolver defaultResolver = DefaultJsonTypeInfoResolver.RootDefaultInstance(); _typeInfoResolver ??= defaultResolver; IsImmutable = true; + _isInitializedForReflectionSerializer = true; } + internal bool IsInitializedForReflectionSerializer => _isInitializedForReflectionSerializer; + private volatile bool _isInitializedForReflectionSerializer; + internal void InitializeForMetadataGeneration() { if (_typeInfoResolver is null) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs index 2b41583d5dd92..b220ffd2da1f1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs @@ -271,8 +271,8 @@ public JsonPolymorphismOptions? PolymorphismOptions // If enumerable or dictionary, the JsonTypeInfo for the element type. private JsonTypeInfo? _elementTypeInfo; - // Avoids having to perform an expensive cast to JsonTypeInfo to check if there is a Serialize method. - internal bool HasSerializeHandler { get; private protected set; } + // Flag indicating that JsonTypeInfo.SerializeHandler is populated and is compatible with the associated Options instance. + internal bool CanUseSerializeHandler { get; private protected set; } // Configure would normally have thrown why initializing properties for source gen but type had SerializeHandler // so it is allowed to be used for fast-path serialization but it will throw if used for metadata-based serialization @@ -540,6 +540,8 @@ internal void Configure() PropertyInfoForTypeInfo.EnsureChildOf(this); PropertyInfoForTypeInfo.EnsureConfigured(); + CanUseSerializeHandler &= Options.SerializerContext?.CanUseSerializationLogic == true; + JsonConverter converter = Converter; Debug.Assert(PropertyInfoForTypeInfo.ConverterStrategy == Converter.ConverterStrategy, $"ConverterStrategy from PropertyInfoForTypeInfo.ConverterStrategy ({PropertyInfoForTypeInfo.ConverterStrategy}) does not match converter's ({Converter.ConverterStrategy})"); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs index 005c67fe578e0..769ac7add4cd5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs @@ -16,6 +16,8 @@ public abstract class JsonTypeInfo : JsonTypeInfo private Func? _typedCreateObject; + internal JsonConverter EffectiveConverter { get; } + /// /// Gets or sets a parameterless factory to be used on deserialization. /// @@ -81,7 +83,9 @@ private protected override void SetCreateObject(Delegate? createObject) internal JsonTypeInfo(JsonConverter converter, JsonSerializerOptions options) : base(typeof(T), converter, options) - { } + { + EffectiveConverter = converter is JsonConverter jsonConverter ? jsonConverter : converter.CreateCastingConverter(); + } /// /// Serializes an instance of using @@ -99,7 +103,7 @@ private protected set { Debug.Assert(!IsConfigured, "We should not mutate configured JsonTypeInfo"); _serialize = value; - HasSerializeHandler = value != null; + CanUseSerializeHandler = value != null; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs index b711b3d303d06..ee348f63aae55 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs @@ -93,13 +93,7 @@ private void EnsurePushCapacity() } } - public void Initialize(Type type, JsonSerializerOptions options, bool supportContinuation) - { - JsonTypeInfo jsonTypeInfo = options.GetTypeInfoForRootType(type); - Initialize(jsonTypeInfo, supportContinuation); - } - - internal void Initialize(JsonTypeInfo jsonTypeInfo, bool supportContinuation = false) + internal void Initialize(JsonTypeInfo jsonTypeInfo) { JsonSerializerOptions options = jsonTypeInfo.Options; if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve) @@ -108,7 +102,6 @@ internal void Initialize(JsonTypeInfo jsonTypeInfo, bool supportContinuation = f PreserveReferences = true; } - SupportContinuation = supportContinuation; Current.JsonTypeInfo = jsonTypeInfo; Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo; Current.NumberHandling = Current.JsonPropertyInfo.EffectiveNumberHandling; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs index 0e15cce45faed..18a36d8680d7b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs @@ -93,6 +93,12 @@ public ref WriteStackFrame Parent /// public bool IsContinuation => _continuationCount != 0; + /// + /// Indicates that the root-level JsonTypeInfo is the result of + /// polymorphic dispatch from the internal System.Object converter. + /// + public bool IsPolymorphicRootValue; + // The bag of preservable references. public ReferenceResolver ReferenceResolver; @@ -133,18 +139,10 @@ private void EnsurePushCapacity() } } - /// - /// Initialize the state without delayed initialization of the JsonTypeInfo. - /// - public JsonConverter Initialize(Type type, JsonSerializerOptions options, bool supportContinuation, bool supportAsync) - { - JsonTypeInfo jsonTypeInfo = options.GetTypeInfoForRootType(type); - return Initialize(jsonTypeInfo, supportContinuation, supportAsync); - } - - internal JsonConverter Initialize(JsonTypeInfo jsonTypeInfo, bool supportContinuation, bool supportAsync) + internal void Initialize(JsonTypeInfo jsonTypeInfo) { - Debug.Assert(!supportAsync || supportContinuation, "supportAsync implies supportContinuation."); + Debug.Assert(!IsContinuation); + Debug.Assert(CurrentDepth == 0); Current.JsonTypeInfo = jsonTypeInfo; Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo; @@ -156,11 +154,6 @@ internal JsonConverter Initialize(JsonTypeInfo jsonTypeInfo, bool supportContinu Debug.Assert(options.ReferenceHandler != null); ReferenceResolver = options.ReferenceHandler.CreateResolver(writing: true); } - - SupportContinuation = supportContinuation; - SupportAsync = supportAsync; - - return jsonTypeInfo.Converter; } /// diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Dynamic.Sample.Tests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Dynamic.Sample.Tests.cs index 5cdae55d64805..9ddd993d5e692 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Dynamic.Sample.Tests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Dynamic.Sample.Tests.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; -using System.Dynamic; using System.Globalization; using System.Tests; using System.Text.Json.Serialization.Samples; diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Dynamic.Sample.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Dynamic.Sample.cs index f5f417ea25c00..f292e80249d94 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Dynamic.Sample.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Dynamic.Sample.cs @@ -514,7 +514,7 @@ public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOp value = dynamicType.Value; } - JsonSerializer.Serialize(writer, value, options); + JsonSerializer.Serialize(writer, value, value.GetType(), options); } private void ReadList(JsonDynamicArray dynamicArray, ref Utf8JsonReader reader, JsonSerializerOptions options) diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Object.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Object.cs index 36309616eaf5f..7e3f8247023f7 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Object.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Object.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; +using System.Text.Json.Serialization.Metadata; using Xunit; namespace System.Text.Json.Serialization.Tests @@ -8,7 +10,7 @@ namespace System.Text.Json.Serialization.Tests public static partial class CustomConverterTests { /// - /// A converter that uses Object as it's type. + /// A converter that uses Object as its type. /// private class ObjectToCustomerOrIntConverter : JsonConverter { @@ -307,9 +309,19 @@ public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) { - Assert.IsType(value); - writer.WriteStartObject(); - writer.WriteEndObject(); + if (value is null) + { + writer.WriteNullValue(); + } + else if (value.GetType() == typeof(object)) + { + writer.WriteStartObject(); + writer.WriteEndObject(); + } + else + { + JsonSerializer.Serialize(writer, value, value.GetType(), options); + } } } @@ -745,6 +757,33 @@ public static void CanCustomizeSystemObjectSerialization() Assert.Equal(expectedJson, actualJson); } + [Theory] + [InlineData(-2)] + [InlineData(false)] + [InlineData("string")] + [InlineData(3.1415926)] + public static void CustomSystemObjectConverter_DoesNotUsePolymorphismInAllContexts(object value) + { + // Regression test for https://github.com/dotnet/runtime/issues/72681 + + var options = new JsonSerializerOptions { Converters = { new CustomSystemObjectConverter() } }; + + TestValue(value, "42"); + TestValue(new { Value = value }, """{"Value":42}"""); + TestValue(new object[] { value }, "[42]"); + TestValue(new Dictionary { ["key"] = value }, """{"key":42}"""); + + void TestValue(T value, string expectedJson) + { + string json = JsonSerializer.Serialize(value, options); + Assert.Equal(expectedJson, json); + + JsonTypeInfo jsonTypeInfo = (JsonTypeInfo)options.GetTypeInfo(typeof(T)); + json = JsonSerializer.Serialize(value, jsonTypeInfo); + Assert.Equal(expectedJson, json); + } + } + private class CustomSystemObjectConverter : JsonConverter { public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotImplementedException(); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs index aff1af118432b..13669fceaae31 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs @@ -935,7 +935,6 @@ public class DerivedClass2 : DerivedClass } [Theory] - [ActiveIssue("https://github.com/dotnet/runtime/issues/72187")] [MemberData(nameof(PolymorphicClass_WithBaseTypeDiscriminator.GetTestData), MemberType = typeof(PolymorphicClass_WithBaseTypeDiscriminator))] public async Task PolymorphicClass_BoxedSerialization_DoesNotUseTypeDiscriminators(PolymorphicClass_WithBaseTypeDiscriminator value, string expectedJson) { diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.cs index 40ecc53a60925..56158ea03b192 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; +using System.Text.Json.Serialization.Metadata; using System.Threading.Tasks; using Xunit; @@ -60,34 +61,23 @@ public PolymorphicTests(JsonSerializerWrapper serializer) : base(serializer) { } - [Fact] - public async Task PrimitivesAsRootObject() + [Theory] + [InlineData(1, "1")] + [InlineData("stringValue", @"""stringValue""")] + [InlineData(true, "true")] + [InlineData(null, "null")] + [InlineData(new int[] { 1, 2, 3}, "[1,2,3]")] + public async Task PrimitivesAsRootObject(object? value, string expectedJson) { - string json = await Serializer.SerializeWrapper(1); - Assert.Equal("1", json); - json = await Serializer.SerializeWrapper(1, typeof(object)); - Assert.Equal("1", json); - - json = await Serializer.SerializeWrapper("foo"); - Assert.Equal(@"""foo""", json); - json = await Serializer.SerializeWrapper("foo", typeof(object)); - Assert.Equal(@"""foo""", json); - - json = await Serializer.SerializeWrapper(true); - Assert.Equal(@"true", json); - json = await Serializer.SerializeWrapper(true, typeof(object)); - Assert.Equal(@"true", json); - - json = await Serializer.SerializeWrapper(null); - Assert.Equal(@"null", json); - json = await Serializer.SerializeWrapper((object)null, typeof(object)); - Assert.Equal(@"null", json); - - decimal pi = 3.1415926535897932384626433833m; - json = await Serializer.SerializeWrapper(pi); - Assert.Equal(@"3.1415926535897932384626433833", json); - json = await Serializer.SerializeWrapper(pi, typeof(object)); - Assert.Equal(@"3.1415926535897932384626433833", json); + string json = await Serializer.SerializeWrapper(value); + Assert.Equal(expectedJson, json); + json = await Serializer.SerializeWrapper(value, typeof(object)); + Assert.Equal(expectedJson, json); + + var options = new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; + JsonTypeInfo objectTypeInfo = (JsonTypeInfo)options.GetTypeInfo(typeof(object)); + json = await Serializer.SerializeWrapper(value, objectTypeInfo); + Assert.Equal(expectedJson, json); } [Fact]