Refactor root-level serialization logic and polymorphic value handling. (#72789)
authorEirik Tsarpalis <eirik.tsarpalis@gmail.com>
Fri, 29 Jul 2022 19:32:35 +0000 (20:32 +0100)
committerGitHub <noreply@github.com>
Fri, 29 Jul 2022 19:32:35 +0000 (20:32 +0100)
* Refactor root-level serialization logic and polymorphic value handling.

* Address feedback

* Do not consult metadata LRU cache in JsonResumableConverter bridging logic.

* Use secondary LRU cache when resolving root-level polymorphic types.

* Add test coverage for root-level polymorphic values in JsonTypeInfo<T> JsonSerializer methods.

* Change caching strategy for root-level polymorphic values.

* Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Helpers.cs

* Remove commented out code

34 files changed:
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/CastingConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.MetadataHandling.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.ReadCore.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.WriteCore.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonResumableConverterOfT.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Helpers.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Document.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Element.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Helpers.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Node.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Span.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Utf8JsonReader.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Document.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Element.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Node.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Utf8JsonWriter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Dynamic.Sample.Tests.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Dynamic.Sample.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Object.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.CustomTypeHierarchies.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.cs

index 72f8dd8..0242cb2 100644 (file)
@@ -25,6 +25,7 @@ namespace System.Text.Json.Serialization.Converters
             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 @@ namespace System.Text.Json.Serialization.Converters
 
         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!;
         }
     }
index d921c7b..fd8e636 100644 (file)
@@ -70,13 +70,12 @@ namespace System.Text.Json.Serialization.Converters
             Debug.Assert(options == jsonTypeInfo.Options);
 
             if (!state.SupportContinuation &&
-                jsonTypeInfo.HasSerializeHandler &&
-                jsonTypeInfo is JsonTypeInfo<T> 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<T> typeInfo && typeInfo.SerializeHandler != null);
+                Debug.Assert(options.SerializerContext?.CanUseSerializationLogic == true);
+                ((JsonTypeInfo<T>)jsonTypeInfo).SerializeHandler!(writer, value);
                 return true;
             }
 
index 15181a2..a507eff 100644 (file)
@@ -80,6 +80,16 @@ namespace System.Text.Json.Serialization
                 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)
index 9c91ac3..c327ada 100644 (file)
@@ -28,7 +28,7 @@ namespace System.Text.Json.Serialization
                     {
                         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)
index 98fbbcb..7b00332 100644 (file)
@@ -13,14 +13,14 @@ namespace System.Text.Json.Serialization
         {
             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<T>.
-                if (value == null && Nullable.GetUnderlyingType(TypeToConvert) == null)
+                if (default(T) is not null && value is null)
                 {
                     ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
                 }
index 69287a5..de61e59 100644 (file)
@@ -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
 {
     /// <summary>
@@ -20,7 +22,9 @@ namespace System.Text.Json.Serialization
             // 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 @@ namespace System.Text.Json.Serialization
             }
 
             // 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);
index c682b28..0e5c16b 100644 (file)
@@ -15,31 +15,42 @@ namespace System.Text.Json
 
         [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<object> 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<T> GetTypeInfo<T>(JsonSerializerOptions? options)
+            => (JsonTypeInfo<T>)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;
         }
 
index f49d96e..58dd06c 100644 (file)
@@ -105,6 +105,7 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
             }
 
+            jsonTypeInfo.EnsureConfigured();
             return ReadDocument<TValue>(document, jsonTypeInfo);
         }
 
index f9cd108..debacec 100644 (file)
@@ -85,6 +85,7 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
             }
 
+            jsonTypeInfo.EnsureConfigured();
             return ReadUsingMetadata<TValue>(element, jsonTypeInfo);
         }
 
index 6a157f9..f3cb5b7 100644 (file)
@@ -9,45 +9,45 @@ namespace System.Text.Json
 {
     public static partial class JsonSerializer
     {
-        private static TValue? ReadCore<TValue>(JsonConverter jsonConverter, ref Utf8JsonReader reader, JsonSerializerOptions options, ref ReadStack state)
+        private static TValue? ReadCore<TValue>(ref Utf8JsonReader reader, JsonTypeInfo jsonTypeInfo, ref ReadStack state)
         {
-            if (jsonConverter is JsonConverter<TValue> converter)
+            if (jsonTypeInfo is JsonTypeInfo<TValue> 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<T>.
-            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<TValue>(ReadOnlySpan<byte> 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<TValue> converter)
+            if (jsonTypeInfo is JsonTypeInfo<TValue> 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<T>.
-                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;
             }
 
index 08f7703..21a3672 100644 (file)
@@ -84,6 +84,7 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
             }
 
+            jsonTypeInfo.EnsureConfigured();
             return ReadNode<TValue>(node, jsonTypeInfo);
         }
 
index 8708f4a..2e0f50c 100644 (file)
@@ -88,6 +88,7 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
             }
 
+            jsonTypeInfo.EnsureConfigured();
             return ReadFromSpan<TValue>(utf8Json, jsonTypeInfo);
         }
 
index ae344a1..833b391 100644 (file)
@@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis;
 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 @@ namespace System.Text.Json
             }
 
             JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, typeof(TValue));
-            return ReadAllAsync<TValue>(utf8Json, jsonTypeInfo, cancellationToken);
+            return ReadFromStreamAsync<TValue>(utf8Json, jsonTypeInfo, cancellationToken);
         }
 
         /// <summary>
@@ -85,7 +86,8 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(utf8Json));
             }
 
-            return ReadAllUsingOptions<TValue>(utf8Json, typeof(TValue), options);
+            JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, typeof(TValue));
+            return ReadFromStream<TValue>(utf8Json, jsonTypeInfo);
         }
 
         /// <summary>
@@ -129,7 +131,7 @@ namespace System.Text.Json
             }
 
             JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, returnType);
-            return ReadAllAsync<object?>(utf8Json, jsonTypeInfo, cancellationToken);
+            return ReadFromStreamAsync<object?>(utf8Json, jsonTypeInfo, cancellationToken);
         }
 
         /// <summary>
@@ -168,7 +170,8 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(returnType));
             }
 
-            return ReadAllUsingOptions<object>(utf8Json, returnType, options);
+            JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, returnType);
+            return ReadFromStream<object>(utf8Json, jsonTypeInfo);
         }
 
         /// <summary>
@@ -208,7 +211,8 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
             }
 
-            return ReadAllAsync<TValue>(utf8Json, jsonTypeInfo, cancellationToken);
+            jsonTypeInfo.EnsureConfigured();
+            return ReadFromStreamAsync<TValue>(utf8Json, jsonTypeInfo, cancellationToken);
         }
 
         /// <summary>
@@ -244,7 +248,8 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
             }
 
-            return ReadAll<TValue>(utf8Json, jsonTypeInfo);
+            jsonTypeInfo.EnsureConfigured();
+            return ReadFromStream<TValue>(utf8Json, jsonTypeInfo);
         }
 
         /// <summary>
@@ -293,7 +298,8 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(context));
             }
 
-            return ReadAllAsync<object>(utf8Json, GetTypeInfo(context, returnType), cancellationToken);
+            JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, returnType);
+            return ReadFromStreamAsync<object>(utf8Json, jsonTypeInfo, cancellationToken);
         }
 
         /// <summary>
@@ -338,7 +344,8 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(context));
             }
 
-            return ReadAll<object>(utf8Json, GetTypeInfo(context, returnType));
+            JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, returnType);
+            return ReadFromStream<object>(utf8Json, jsonTypeInfo);
         }
 
         /// <summary>
@@ -403,7 +410,7 @@ namespace System.Text.Json
 
         private static JsonTypeInfo<Queue<TValue>> CreateQueueTypeInfo<TValue>(JsonTypeInfo jsonTypeInfo)
         {
-            return JsonMetadataServices.CreateQueueInfo<Queue<TValue>, TValue>(
+            JsonTypeInfo<Queue<TValue>> queueTypeInfo = JsonMetadataServices.CreateQueueInfo<Queue<TValue>, TValue>(
                 options: jsonTypeInfo.Options,
                 collectionInfo: new()
                 {
@@ -411,6 +418,9 @@ namespace System.Text.Json
                     ElementInfo = jsonTypeInfo,
                     NumberHandling = jsonTypeInfo.Options.NumberHandling
                 });
+
+            queueTypeInfo.EnsureConfigured();
+            return queueTypeInfo;
         }
 
         private static async IAsyncEnumerable<TValue> CreateAsyncEnumerableDeserializer<TValue>(
@@ -418,11 +428,16 @@ namespace System.Text.Json
             JsonTypeInfo<Queue<TValue>> 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 @@ namespace System.Text.Json
                         ref bufferState,
                         ref jsonReaderState,
                         ref readStack,
-                        queueTypeInfo.Converter,
-                        options);
+                        queueTypeInfo);
 
                     if (readStack.Current.ReturnValue is Queue<TValue> queue)
                     {
@@ -453,17 +467,19 @@ namespace System.Text.Json
             }
         }
 
-        internal static async ValueTask<TValue?> ReadAllAsync<TValue>(
+        internal static async ValueTask<TValue?> ReadFromStreamAsync<TValue>(
             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 @@ namespace System.Text.Json
                 while (true)
                 {
                     bufferState = await bufferState.ReadFromStreamAsync(utf8Json, cancellationToken).ConfigureAwait(false);
-                    TValue value = ContinueDeserialize<TValue>(ref bufferState, ref jsonReaderState, ref readStack, converter, options);
+                    TValue? value = ContinueDeserialize<TValue>(ref bufferState, ref jsonReaderState, ref readStack, jsonTypeInfo);
 
                     if (bufferState.IsFinalBlock)
                     {
-                        return value!;
+                        return value;
                     }
                 }
             }
@@ -485,16 +501,18 @@ namespace System.Text.Json
             }
         }
 
-        internal static TValue? ReadAll<TValue>(
+        internal static TValue? ReadFromStream<TValue>(
             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 @@ namespace System.Text.Json
                 while (true)
                 {
                     bufferState.ReadFromStream(utf8Json);
-                    TValue value = ContinueDeserialize<TValue>(ref bufferState, ref jsonReaderState, ref readStack, converter, options);
+                    TValue? value = ContinueDeserialize<TValue>(ref bufferState, ref jsonReaderState, ref readStack, jsonTypeInfo);
 
                     if (bufferState.IsFinalBlock)
                     {
-                        return value!;
+                        return value;
                     }
                 }
             }
@@ -516,59 +534,26 @@ namespace System.Text.Json
             }
         }
 
-        internal static TValue ContinueDeserialize<TValue>(
+        internal static TValue? ContinueDeserialize<TValue>(
             ref ReadBufferState bufferState,
             ref JsonReaderState jsonReaderState,
             ref ReadStack readStack,
-            JsonConverter converter,
-            JsonSerializerOptions options)
-        {
-            // Process the data available
-            TValue value = ReadCore<TValue>(
-                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<TValue>(
-            Stream utf8Json,
-            Type returnType,
-            JsonSerializerOptions? options)
-        {
-            JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, returnType);
-            return ReadAll<TValue>(utf8Json, jsonTypeInfo);
-        }
-
-        private static TValue ReadCore<TValue>(
-            ref JsonReaderState readerState,
-            bool isFinalBlock,
-            ReadOnlySpan<byte> 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<TValue>(converterBase, ref reader, options, ref state);
-            readerState = reader.CurrentState;
-            return value!;
+            TValue? value = ReadCore<TValue>(ref reader, jsonTypeInfo, ref readStack);
+            Debug.Assert(readStack.BytesConsumed <= bufferState.Bytes.Length);
+            bufferState.AdvanceBuffer((int)readStack.BytesConsumed);
+            jsonReaderState = reader.CurrentState;
+            return value;
         }
     }
 }
index e9bb1f1..1a307f3 100644 (file)
@@ -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 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
             }
 
+            jsonTypeInfo.EnsureConfigured();
             return ReadFromSpan<TValue?>(json.AsSpan(), jsonTypeInfo);
         }
 
@@ -257,6 +259,7 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
             }
 
+            jsonTypeInfo.EnsureConfigured();
             return ReadFromSpan<TValue?>(json, jsonTypeInfo);
         }
 
@@ -366,7 +369,7 @@ namespace System.Text.Json
 
         private static TValue? ReadFromSpan<TValue>(ReadOnlySpan<char> 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.
index 5fb3adb..b29d7d5 100644 (file)
@@ -170,6 +170,7 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
             }
 
+            jsonTypeInfo.EnsureConfigured();
             return Read<TValue>(ref reader, jsonTypeInfo);
         }
 
@@ -238,8 +239,8 @@ namespace System.Text.Json
 
         private static TValue? Read<TValue>(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 @@ namespace System.Text.Json
 
                 var newReader = new Utf8JsonReader(rentedSpan, originalReaderOptions);
 
-                JsonConverter jsonConverter = state.Current.JsonPropertyInfo!.EffectiveConverter;
-                TValue? value = ReadCore<TValue>(jsonConverter, ref newReader, jsonTypeInfo.Options, ref state);
+                TValue? value = ReadCore<TValue>(ref newReader, jsonTypeInfo, ref state);
 
                 // The reader should have thrown if we have remaining bytes.
                 Debug.Assert(newReader.BytesConsumed == length);
index dda1088..49aa791 100644 (file)
@@ -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 @@ namespace System.Text.Json
             TValue value,
             JsonSerializerOptions? options = null)
         {
-            Type runtimeType = GetRuntimeType(value);
-            JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
-            return WriteBytesUsingSerializer(value, jsonTypeInfo);
+            JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
+            return WriteBytes(value, jsonTypeInfo);
         }
 
         /// <summary>
@@ -54,9 +54,9 @@ namespace System.Text.Json
             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);
         }
 
         /// <summary>
@@ -79,7 +79,8 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
             }
 
-            return WriteBytesUsingGeneratedSerializer(value, jsonTypeInfo);
+            jsonTypeInfo.EnsureConfigured();
+            return WriteBytes(value, jsonTypeInfo);
         }
 
         /// <summary>
@@ -110,34 +111,33 @@ namespace System.Text.Json
                 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<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
+        private static byte[] WriteBytes<TValue>(in TValue value, JsonTypeInfo<TValue> 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<TValue>(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();
         }
     }
index 731dd0b..13ff63a 100644 (file)
@@ -25,9 +25,8 @@ namespace System.Text.Json
         [RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
         public static JsonDocument SerializeToDocument<TValue>(TValue value, JsonSerializerOptions? options = null)
         {
-            Type runtimeType = GetRuntimeType(value);
-            JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
-            return WriteDocumentUsingSerializer(value, jsonTypeInfo);
+            JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
+            return WriteDocument(value, jsonTypeInfo);
         }
 
         /// <summary>
@@ -51,9 +50,9 @@ namespace System.Text.Json
         [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);
         }
 
         /// <summary>
@@ -77,7 +76,8 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
             }
 
-            return WriteDocumentUsingGeneratedSerializer(value, jsonTypeInfo);
+            jsonTypeInfo.EnsureConfigured();
+            return WriteDocument(value, jsonTypeInfo);
         }
 
         /// <summary>
@@ -105,39 +105,35 @@ namespace System.Text.Json
                 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<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
+        private static JsonDocument WriteDocument<TValue>(in TValue value, JsonTypeInfo<TValue> 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<TValue>(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());
         }
     }
index 3343f54..ba35d4c 100644 (file)
@@ -25,9 +25,8 @@ namespace System.Text.Json
         [RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
         public static JsonElement SerializeToElement<TValue>(TValue value, JsonSerializerOptions? options = null)
         {
-            Type runtimeType = GetRuntimeType(value);
-            JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
-            return WriteElementUsingSerializer(value, jsonTypeInfo);
+            JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
+            return WriteElement(value, jsonTypeInfo);
         }
 
         /// <summary>
@@ -51,9 +50,9 @@ namespace System.Text.Json
         [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);
         }
 
         /// <summary>
@@ -77,7 +76,8 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
             }
 
-            return WriteElementUsingGeneratedSerializer(value, jsonTypeInfo);
+            jsonTypeInfo.EnsureConfigured();
+            return WriteElement(value, jsonTypeInfo);
         }
 
         /// <summary>
@@ -105,38 +105,34 @@ namespace System.Text.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<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
+        private static JsonElement WriteElement<TValue>(in TValue value, JsonTypeInfo<TValue> 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<TValue>(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());
         }
     }
index d80d5fb..1f688d4 100644 (file)
 // 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<TValue>(
-            JsonConverter jsonConverter,
+        /// <summary>
+        /// Sync, strongly typed root value serialization helper.
+        /// </summary>
+        private static void WriteCore<TValue>(
             Utf8JsonWriter writer,
             in TValue value,
-            JsonSerializerOptions options,
-            ref WriteStack state)
+            JsonTypeInfo<TValue> 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<TValue>);
 
-            if (jsonConverter is JsonConverter<TValue> 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<T>.
-                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<TValue>(Utf8JsonWriter writer, in TValue value, JsonTypeInfo jsonTypeInfo)
+        /// <summary>
+        /// Sync, untyped root value serialization helper.
+        /// </summary>
+        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<TValue> 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<TValue>(Utf8JsonWriter writer, in TValue value, JsonTypeInfo jsonTypeInfo)
+        /// <summary>
+        /// Streaming root-level serialization helper.
+        /// </summary>
+        private static bool WriteCore<TValue>(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<TValue> typedConverter)
+            bool isFinalBlock;
+            if (jsonTypeInfo is JsonTypeInfo<TValue> 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<T>.
-                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<TValue>(in TValue value)
+        private static JsonTypeInfo ResolvePolymorphicTypeInfo<TValue>(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 @@ namespace System.Text.Json
                 {
                     ThrowHelper.ThrowArgumentException_DeserializeWrongType(inputType, value);
                 }
-
-                if (inputType == JsonTypeInfo.ObjectType)
-                {
-                    return runtimeType;
-                }
             }
-
-            return inputType;
         }
     }
 }
index 5f24f4f..379b804 100644 (file)
@@ -26,9 +26,8 @@ namespace System.Text.Json
         [RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
         public static JsonNode? SerializeToNode<TValue>(TValue value, JsonSerializerOptions? options = null)
         {
-            Type runtimeType = GetRuntimeType(value);
-            JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
-            return WriteNodeUsingSerializer(value, jsonTypeInfo);
+            JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
+            return WriteNode(value, jsonTypeInfo);
         }
 
         /// <summary>
@@ -52,9 +51,9 @@ namespace System.Text.Json
         [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);
         }
 
         /// <summary>
@@ -78,7 +77,8 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
             }
 
-            return WriteNodeUsingGeneratedSerializer(value, jsonTypeInfo);
+            jsonTypeInfo.EnsureConfigured();
+            return WriteNode(value, jsonTypeInfo);
         }
 
         /// <summary>
@@ -106,38 +106,34 @@ namespace System.Text.Json
                 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<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
+        private static JsonNode? WriteNode<TValue>(in TValue value, JsonTypeInfo<TValue> 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<TValue>(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());
         }
     }
index 9fb7c5c..d6c4734 100644 (file)
@@ -50,9 +50,8 @@ namespace System.Text.Json
                 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);
         }
 
         /// <summary>
@@ -81,9 +80,8 @@ namespace System.Text.Json
                 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);
         }
 
         /// <summary>
@@ -119,9 +117,9 @@ namespace System.Text.Json
                 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);
         }
 
         /// <summary>
@@ -154,9 +152,9 @@ namespace System.Text.Json
                 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);
         }
 
         /// <summary>
@@ -259,11 +257,11 @@ namespace System.Text.Json
                 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 @@ namespace System.Text.Json
                 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<TValue>(
@@ -309,15 +307,22 @@ namespace System.Text.Json
             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 @@ namespace System.Text.Json
 
                         try
                         {
-                            isFinalBlock = WriteCore(converter, writer, value, options, ref state);
+                            isFinalBlock = WriteCore(writer, value, jsonTypeInfo, ref state);
 
                             if (state.SuppressFlush)
                             {
@@ -383,15 +388,20 @@ namespace System.Text.Json
             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 @@ namespace System.Text.Json
                 {
                     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();
index 674a306..451565a 100644 (file)
@@ -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 @@ namespace System.Text.Json
         [RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
         public static string Serialize<TValue>(TValue value, JsonSerializerOptions? options = null)
         {
-            Type runtimeType = GetRuntimeType(value);
-            JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
-            return WriteStringUsingSerializer(value, jsonTypeInfo);
+            JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
+            return WriteString(value, jsonTypeInfo);
         }
 
         /// <summary>
@@ -61,9 +61,9 @@ namespace System.Text.Json
             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);
         }
 
         /// <summary>
@@ -86,7 +86,13 @@ namespace System.Text.Json
         /// </remarks>
         public static string Serialize<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo)
         {
-            return WriteStringUsingGeneratedSerializer(value, jsonTypeInfo);
+            if (jsonTypeInfo is null)
+            {
+                ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
+            }
+
+            jsonTypeInfo.EnsureConfigured();
+            return WriteString(value, jsonTypeInfo);
         }
 
         /// <summary>
@@ -118,49 +124,35 @@ namespace System.Text.Json
                 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<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
+        private static string WriteString<TValue>(in TValue value, JsonTypeInfo<TValue> 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<TValue>(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);
         }
     }
 }
index 7f27dcd..20ffe8d 100644 (file)
@@ -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 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(writer));
             }
 
-            Type runtimeType = GetRuntimeType(value);
-            JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
-            WriteUsingSerializer(writer, value, jsonTypeInfo);
+            JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
+            WriteCore(writer, value, jsonTypeInfo);
         }
 
         /// <summary>
@@ -70,9 +70,9 @@ namespace System.Text.Json
                 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);
         }
 
         /// <summary>
@@ -100,7 +100,8 @@ namespace System.Text.Json
                 ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
             }
 
-            WriteUsingGeneratedSerializer(writer, value, jsonTypeInfo);
+            jsonTypeInfo.EnsureConfigured();
+            WriteCore(writer, value, jsonTypeInfo);
         }
 
         /// <summary>
@@ -135,8 +136,9 @@ namespace System.Text.Json
                 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);
         }
     }
 }
index cad93de..f0a7e71 100644 (file)
@@ -100,17 +100,29 @@ namespace System.Text.Json
 
             if (jsonTypeInfo?.Type != type)
             {
-                jsonTypeInfo = GetTypeInfoInternal(type);
-                _lastTypeInfo = jsonTypeInfo;
+                _lastTypeInfo = jsonTypeInfo = GetTypeInfoInternal(type);
             }
 
             return jsonTypeInfo;
         }
 
+        // Caches the resolved JsonTypeInfo<object> 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()
index b6898db..596a9c3 100644 (file)
@@ -630,8 +630,12 @@ namespace System.Text.Json
             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)
index 2b41583..b220ffd 100644 (file)
@@ -271,8 +271,8 @@ namespace System.Text.Json.Serialization.Metadata
         // If enumerable or dictionary, the JsonTypeInfo for the element type.
         private JsonTypeInfo? _elementTypeInfo;
 
-        // Avoids having to perform an expensive cast to JsonTypeInfo<T> to check if there is a Serialize method.
-        internal bool HasSerializeHandler { get; private protected set; }
+        // Flag indicating that JsonTypeInfo<T>.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 @@ namespace System.Text.Json.Serialization.Metadata
             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})");
index 005c67f..769ac7a 100644 (file)
@@ -16,6 +16,8 @@ namespace System.Text.Json.Serialization.Metadata
 
         private Func<T>? _typedCreateObject;
 
+        internal JsonConverter<T> EffectiveConverter { get; }
+
         /// <summary>
         /// Gets or sets a parameterless factory to be used on deserialization.
         /// </summary>
@@ -81,7 +83,9 @@ namespace System.Text.Json.Serialization.Metadata
 
         internal JsonTypeInfo(JsonConverter converter, JsonSerializerOptions options)
             : base(typeof(T), converter, options)
-        { }
+        {
+            EffectiveConverter = converter is JsonConverter<T> jsonConverter ? jsonConverter : converter.CreateCastingConverter<T>();
+        }
 
         /// <summary>
         /// Serializes an instance of <typeparamref name="T"/> using
@@ -99,7 +103,7 @@ namespace System.Text.Json.Serialization.Metadata
             {
                 Debug.Assert(!IsConfigured, "We should not mutate configured JsonTypeInfo");
                 _serialize = value;
-                HasSerializeHandler = value != null;
+                CanUseSerializeHandler = value != null;
             }
         }
 
index b711b3d..ee348f6 100644 (file)
@@ -93,13 +93,7 @@ namespace System.Text.Json
             }
         }
 
-        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 @@ namespace System.Text.Json
                 PreserveReferences = true;
             }
 
-            SupportContinuation = supportContinuation;
             Current.JsonTypeInfo = jsonTypeInfo;
             Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
             Current.NumberHandling = Current.JsonPropertyInfo.EffectiveNumberHandling;
index 0e15cce..18a36d8 100644 (file)
@@ -93,6 +93,12 @@ namespace System.Text.Json
         /// </summary>
         public bool IsContinuation => _continuationCount != 0;
 
+        /// <summary>
+        /// Indicates that the root-level JsonTypeInfo is the result of
+        /// polymorphic dispatch from the internal System.Object converter.
+        /// </summary>
+        public bool IsPolymorphicRootValue;
+
         // The bag of preservable references.
         public ReferenceResolver ReferenceResolver;
 
@@ -133,18 +139,10 @@ namespace System.Text.Json
             }
         }
 
-        /// <summary>
-        /// Initialize the state without delayed initialization of the JsonTypeInfo.
-        /// </summary>
-        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 @@ namespace System.Text.Json
                 Debug.Assert(options.ReferenceHandler != null);
                 ReferenceResolver = options.ReferenceHandler.CreateResolver(writing: true);
             }
-
-            SupportContinuation = supportContinuation;
-            SupportAsync = supportAsync;
-
-            return jsonTypeInfo.Converter;
         }
 
         /// <summary>
index 5cdae55..9ddd993 100644 (file)
@@ -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;
index f5f417e..f292e80 100644 (file)
@@ -514,7 +514,7 @@ namespace System.Text.Json.Serialization.Samples
                     value = dynamicType.Value;
                 }
 
-                JsonSerializer.Serialize<object>(writer, value, options);
+                JsonSerializer.Serialize(writer, value, value.GetType(), options);
             }
 
             private void ReadList(JsonDynamicArray dynamicArray, ref Utf8JsonReader reader, JsonSerializerOptions options)
index 3630961..7e3f824 100644 (file)
@@ -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
     {
         /// <summary>
-        /// A converter that uses Object as it's type.
+        /// A converter that uses Object as its type.
         /// </summary>
         private class ObjectToCustomerOrIntConverter : JsonConverter<object>
         {
@@ -307,9 +309,19 @@ namespace System.Text.Json.Serialization.Tests
 
             public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
             {
-                Assert.IsType<object>(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 @@ namespace System.Text.Json.Serialization.Tests
             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<string, object> { ["key"] = value }, """{"key":42}""");
+
+            void TestValue<T>(T value, string expectedJson)
+            {
+                string json = JsonSerializer.Serialize(value, options);
+                Assert.Equal(expectedJson, json);
+
+                JsonTypeInfo<T> jsonTypeInfo = (JsonTypeInfo<T>)options.GetTypeInfo(typeof(T));
+                json = JsonSerializer.Serialize(value, jsonTypeInfo);
+                Assert.Equal(expectedJson, json);
+            }
+        }
+
         private class CustomSystemObjectConverter : JsonConverter<object>
         {
             public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotImplementedException();
index aff1af1..13669fc 100644 (file)
@@ -935,7 +935,6 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [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)
         {
index 40ecc53..56158ea 100644 (file)
@@ -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 @@ namespace System.Text.Json.Serialization.Tests
         {
         }
 
-        [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<object>(1);
-            Assert.Equal("1", json);
-            json = await Serializer.SerializeWrapper(1, typeof(object));
-            Assert.Equal("1", json);
-
-            json = await Serializer.SerializeWrapper<object>("foo");
-            Assert.Equal(@"""foo""", json);
-            json = await Serializer.SerializeWrapper("foo", typeof(object));
-            Assert.Equal(@"""foo""", json);
-
-            json = await Serializer.SerializeWrapper<object>(true);
-            Assert.Equal(@"true", json);
-            json = await Serializer.SerializeWrapper(true, typeof(object));
-            Assert.Equal(@"true", json);
-
-            json = await Serializer.SerializeWrapper<object>(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<object>(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<object> objectTypeInfo = (JsonTypeInfo<object>)options.GetTypeInfo(typeof(object));
+            json = await Serializer.SerializeWrapper(value, objectTypeInfo);
+            Assert.Equal(expectedJson, json);
         }
 
         [Fact]