Remove types deriving from JsonTypeInfo<T> and mark as sealed. (#81306)
authorEirik Tsarpalis <eirik.tsarpalis@gmail.com>
Thu, 2 Feb 2023 14:14:14 +0000 (14:14 +0000)
committerGitHub <noreply@github.com>
Thu, 2 Feb 2023 14:14:14 +0000 (14:14 +0000)
* Remove types deriving from JsonTypeInfo<T> and mark as sealed.

* Rename the metadata resolution files.

* Address feedback

* Only sort properties if necessary.

17 files changed:
src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs
src/libraries/System.Text.Json/ref/System.Text.Json.cs
src/libraries/System.Text.Json/src/System.Text.Json.csproj
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/CustomJsonTypeInfoOfT.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs with 63% similarity]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Collections.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Helpers.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.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/Metadata/SourceGenJsonTypeInfoOfT.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs

index b65b286..ce2fb3f 100644 (file)
@@ -184,7 +184,7 @@ namespace System.Text.Json.SourceGeneration
 
             castingRequiredForProps = false;
             serializableProperties = new Dictionary<string, PropertyGenerationSpec>();
-            Dictionary<string, PropertyGenerationSpec>? ignoredMembers = null;
+            HashSet<string>? ignoredMembers = null;
 
             for (int i = 0; i < PropertyGenSpecList.Count; i++)
             {
@@ -245,7 +245,7 @@ namespace System.Text.Json.SourceGeneration
                                 other.ClrName == memberName ||
                                 // Was a property with the same CLR name ignored? That property hid the current property,
                                 // thus, if it was ignored, the current property should be ignored too.
-                                ignoredMembers?.ContainsKey(memberName) == true;
+                                ignoredMembers?.Contains(memberName) == true;
                         }
                         else
                         {
@@ -271,7 +271,7 @@ namespace System.Text.Json.SourceGeneration
 
                 if (propGenSpec.DefaultIgnoreCondition == JsonIgnoreCondition.Always)
                 {
-                    (ignoredMembers ??= new()).Add(memberName, propGenSpec);
+                    (ignoredMembers ??= new()).Add(memberName);
                 }
             }
 
index d7d1227..6916f71 100644 (file)
@@ -1271,7 +1271,7 @@ namespace System.Text.Json.Serialization.Metadata
     {
         public static System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver Combine(params System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver?[] resolvers) { throw null; }
     }
-    public abstract partial class JsonTypeInfo<T> : System.Text.Json.Serialization.Metadata.JsonTypeInfo
+    public sealed partial class JsonTypeInfo<T> : System.Text.Json.Serialization.Metadata.JsonTypeInfo
     {
         internal JsonTypeInfo() { }
         public new System.Func<T>? CreateObject { get { throw null; } set { } }
index f436941..a149dd4 100644 (file)
@@ -127,6 +127,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
     <Compile Include="System\Text\Json\Serialization\JsonSerializerContext.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonUnknownDerivedTypeHandling.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\DefaultJsonTypeInfoResolver.Converters.cs" />
+    <Compile Include="System\Text\Json\Serialization\Metadata\DefaultJsonTypeInfoResolver.Helpers.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\DefaultJsonTypeInfoResolver.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\IJsonTypeInfoResolver.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonDerivedType.cs" />
@@ -135,7 +136,6 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoOfT.WriteHelpers.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoResolver.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoKind.cs" />
-    <Compile Include="System\Text\Json\Serialization\Metadata\CustomJsonTypeInfoOfT.cs" />
     <Compile Include="System\Text\Json\Serialization\PolymorphicSerializationState.cs" />
     <Compile Include="System\Text\Json\Writer\Utf8JsonWriterCache.cs" />
     <Compile Include="System\Text\Json\Serialization\ReferenceEqualsWrapper.cs" />
@@ -249,6 +249,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonCollectionInfoValuesOfTCollection.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.Collections.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.Converters.cs" />
+    <Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.Helpers.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonObjectInfoValuesOfT.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonParameterInfoValues.cs" />
@@ -257,7 +258,6 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonPropertyInfo.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonPropertyInfoOfT.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonPropertyInfoValuesOfT.cs" />
-    <Compile Include="System\Text\Json\Serialization\Metadata\SourceGenJsonTypeInfoOfT.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoOfT.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfo.Cache.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfo.cs" />
@@ -265,7 +265,6 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
     <Compile Include="System\Text\Json\Serialization\Metadata\ParameterRef.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\PolymorphicTypeResolver.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\PropertyRef.cs" />
-    <Compile Include="System\Text\Json\Serialization\Metadata\ReflectionJsonTypeInfoOfT.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\ReflectionEmitCachingMemberAccessor.Cache.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\ReflectionEmitCachingMemberAccessor.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\ReflectionEmitMemberAccessor.cs" />
index ed2d884..a32063e 100644 (file)
@@ -90,16 +90,7 @@ namespace System.Text.Json.Serialization
             throw new InvalidOperationException();
         }
 
-        [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
-        [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
-        internal virtual JsonTypeInfo CreateReflectionJsonTypeInfo(JsonSerializerOptions options)
-        {
-            Debug.Fail("Should not be reachable.");
-
-            throw new InvalidOperationException();
-        }
-
-        internal virtual JsonTypeInfo CreateCustomJsonTypeInfo(JsonSerializerOptions options)
+        internal virtual JsonTypeInfo CreateJsonTypeInfo(JsonSerializerOptions options)
         {
             Debug.Fail("Should not be reachable.");
 
index 33e3bb9..ff811d8 100644 (file)
@@ -51,16 +51,9 @@ namespace System.Text.Json.Serialization
 
         private protected override ConverterStrategy GetDefaultConverterStrategy() => ConverterStrategy.Value;
 
-        [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
-        [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
-        internal sealed override JsonTypeInfo CreateReflectionJsonTypeInfo(JsonSerializerOptions options)
+        internal sealed override JsonTypeInfo CreateJsonTypeInfo(JsonSerializerOptions options)
         {
-            return new ReflectionJsonTypeInfo<T>(this, options);
-        }
-
-        internal sealed override JsonTypeInfo CreateCustomJsonTypeInfo(JsonSerializerOptions options)
-        {
-            return new CustomJsonTypeInfo<T>(this, options);
+            return new JsonTypeInfo<T>(this, options);
         }
 
         internal sealed override JsonParameterInfo CreateJsonParameterInfo()
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/CustomJsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/CustomJsonTypeInfoOfT.cs
deleted file mode 100644 (file)
index 984125d..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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.CodeAnalysis;
-
-namespace System.Text.Json.Serialization.Metadata
-{
-    /// <summary>
-    /// Creates and initializes serialization metadata for a type.
-    /// </summary>
-    /// <typeparam name="T"></typeparam>
-    internal sealed class CustomJsonTypeInfo<T> : JsonTypeInfo<T>
-    {
-        /// <summary>
-        /// Creates serialization metadata for a type using a simple converter.
-        /// </summary>
-        internal CustomJsonTypeInfo(JsonConverter converter, JsonSerializerOptions options)
-            : base(converter, options)
-        {
-        }
-
-        internal override JsonParameterInfoValues[] GetParameterInfoValues()
-        {
-            // Parameterized constructors not supported yet for custom types
-            return Array.Empty<JsonParameterInfoValues>();
-        }
-    }
-}
@@ -9,89 +9,89 @@ using System.Text.Json.Reflection;
 
 namespace System.Text.Json.Serialization.Metadata
 {
-    /// <summary>
-    /// Provides JSON serialization-related metadata about a type.
-    /// </summary>
-    internal sealed class ReflectionJsonTypeInfo<T> : JsonTypeInfo<T>
+    public partial class DefaultJsonTypeInfoResolver
     {
         [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
         [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
-        internal ReflectionJsonTypeInfo(JsonConverter converter, JsonSerializerOptions options)
-            : base(converter, options)
+        private static JsonTypeInfo CreateTypeInfoCore(Type type, JsonConverter converter, JsonSerializerOptions options)
         {
-            NumberHandling = GetNumberHandlingForType(Type);
-            if (Kind == JsonTypeInfoKind.Object)
+            JsonTypeInfo typeInfo = JsonTypeInfo.CreateJsonTypeInfo(type, converter, options);
+            typeInfo.NumberHandling = GetNumberHandlingForType(typeInfo.Type);
+            if (typeInfo.Kind == JsonTypeInfoKind.Object)
             {
-                UnmappedMemberHandling = GetUnmappedMemberHandling(Type);
+                typeInfo.UnmappedMemberHandling = GetUnmappedMemberHandling(typeInfo.Type);
             }
 
-            PopulatePolymorphismMetadata();
-            MapInterfaceTypesToCallbacks();
+            typeInfo.PopulatePolymorphismMetadata();
+            typeInfo.MapInterfaceTypesToCallbacks();
 
-            Func<object>? createObject = JsonSerializerOptions.MemberAccessorStrategy.CreateConstructor(typeof(T));
-            SetCreateObjectIfCompatible(createObject);
-            CreateObjectForExtensionDataProperty = createObject;
+            Func<object>? createObject = JsonSerializerOptions.MemberAccessorStrategy.CreateConstructor(typeInfo.Type);
+            typeInfo.SetCreateObjectIfCompatible(createObject);
+            typeInfo.CreateObjectForExtensionDataProperty = createObject;
+
+            if (typeInfo.Kind is JsonTypeInfoKind.Object)
+            {
+                PopulateProperties(typeInfo);
+
+                if (converter.ConstructorIsParameterized)
+                {
+                    PopulateParameterInfoValues(typeInfo);
+                }
+            }
 
             // Plug in any converter configuration -- should be run last.
-            converter.ConfigureJsonTypeInfo(this, options);
-            converter.ConfigureJsonTypeInfoUsingReflection(this, options);
+            converter.ConfigureJsonTypeInfo(typeInfo, options);
+            converter.ConfigureJsonTypeInfoUsingReflection(typeInfo, options);
+            return typeInfo;
         }
 
-        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072:UnrecognizedReflectionPattern",
-            Justification = "The ctor is marked RequiresUnreferencedCode.")]
-        internal override void LateAddProperties()
+        [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
+        [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
+        private static void PopulateProperties(JsonTypeInfo typeInfo)
         {
-            Debug.Assert(!IsConfigured);
-            Debug.Assert(PropertyCache is null);
-
-            if (Kind != JsonTypeInfoKind.Object)
-            {
-                return;
-            }
+            Debug.Assert(!typeInfo.IsReadOnly);
+            Debug.Assert(typeInfo.Kind is JsonTypeInfoKind.Object);
 
             // Compiler adds RequiredMemberAttribute to type if any of the members is marked with 'required' keyword.
             // SetsRequiredMembersAttribute means that all required members are assigned by constructor and therefore there is no enforcement
             bool shouldCheckMembersForRequiredMemberAttribute =
-                typeof(T).HasRequiredMemberAttribute()
-                && !(Converter.ConstructorInfo?.HasSetsRequiredMembersAttribute() ?? false);
+                typeInfo.Type.HasRequiredMemberAttribute()
+                && !(typeInfo.Converter.ConstructorInfo?.HasSetsRequiredMembersAttribute() ?? false);
 
-            bool propertyOrderSpecified = false;
-            Dictionary<string, JsonPropertyInfo>? ignoredMembers = null;
+            JsonTypeInfo.PropertyHierarchyResolutionState state = new();
 
             // Walk the type hierarchy starting from the current type up to the base type(s)
-            foreach (Type currentType in Type.GetSortedTypeHierarchy())
+            foreach (Type currentType in typeInfo.Type.GetSortedTypeHierarchy())
             {
-                if (currentType == ObjectType)
+                if (currentType == JsonTypeInfo.ObjectType)
                 {
                     // Don't process any members for typeof(object)
                     break;
                 }
 
                 AddMembersDeclaredBySuperType(
+                    typeInfo,
                     currentType,
                     shouldCheckMembersForRequiredMemberAttribute,
-                    ref propertyOrderSpecified,
-                    ref ignoredMembers);
+                    ref state);
             }
 
-            Debug.Assert(PropertyCache != null);
-
-            if (propertyOrderSpecified)
+            if (state.IsPropertyOrderSpecified)
             {
-                PropertyCache.List.StableSortByKey(static p => p.Value.Order);
+                typeInfo.SortProperties();
             }
         }
 
-        [UnconditionalSuppressMessage("Trimming", "IL2070:UnrecognizedReflectionPattern",
-            Justification = "The ctor is marked RequiresUnreferencedCode.")]
-        private void AddMembersDeclaredBySuperType(
+        [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
+        [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
+        private static void AddMembersDeclaredBySuperType(
+            JsonTypeInfo typeInfo,
             Type currentType,
             bool shouldCheckMembersForRequiredMemberAttribute,
-            ref bool propertyOrderSpecified,
-            ref Dictionary<string, JsonPropertyInfo>? ignoredMembers)
+            ref JsonTypeInfo.PropertyHierarchyResolutionState state)
         {
-            Debug.Assert(!IsConfigured);
-            Debug.Assert(currentType.IsAssignableFrom(Type));
+            Debug.Assert(!typeInfo.IsReadOnly);
+            Debug.Assert(currentType.IsAssignableFrom(typeInfo.Type));
 
             const BindingFlags BindingFlags =
                 BindingFlags.Instance |
@@ -101,18 +101,13 @@ namespace System.Text.Json.Serialization.Metadata
 
             PropertyInfo[] properties = currentType.GetProperties(BindingFlags);
 
-            // PropertyCache is not accessed by other threads until the current JsonTypeInfo instance
-            // is finished initializing and added to the cache in JsonSerializerOptions.
-            // Default 'capacity' to the common non-polymorphic + property case.
-            PropertyCache ??= CreatePropertyCache(capacity: properties.Length);
-
             foreach (PropertyInfo propertyInfo in properties)
             {
                 string propertyName = propertyInfo.Name;
 
                 // Ignore indexers and virtual properties that have overrides that were [JsonIgnore]d.
                 if (propertyInfo.GetIndexParameters().Length > 0 ||
-                    PropertyIsOverriddenAndIgnored(propertyName, propertyInfo.PropertyType, propertyInfo.IsVirtual(), ignoredMembers))
+                    PropertyIsOverriddenAndIgnored(propertyName, propertyInfo.PropertyType, propertyInfo.IsVirtual(), state.IgnoredProperties))
                 {
                     continue;
                 }
@@ -121,12 +116,12 @@ namespace System.Text.Json.Serialization.Metadata
                 if (propertyInfo.GetMethod?.IsPublic == true ||
                     propertyInfo.SetMethod?.IsPublic == true)
                 {
-                    CacheMember(
+                    AddMember(
+                        typeInfo,
                         typeToConvert: propertyInfo.PropertyType,
                         memberInfo: propertyInfo,
-                        ref propertyOrderSpecified,
-                        ref ignoredMembers,
-                        shouldCheckMembersForRequiredMemberAttribute);
+                        shouldCheckMembersForRequiredMemberAttribute,
+                        ref state);
                 }
                 else
                 {
@@ -143,7 +138,7 @@ namespace System.Text.Json.Serialization.Metadata
             {
                 string fieldName = fieldInfo.Name;
 
-                if (PropertyIsOverriddenAndIgnored(fieldName, fieldInfo.FieldType, currentMemberIsVirtual: false, ignoredMembers))
+                if (PropertyIsOverriddenAndIgnored(fieldName, fieldInfo.FieldType, currentMemberIsVirtual: false, state.IgnoredProperties))
                 {
                     continue;
                 }
@@ -152,14 +147,14 @@ namespace System.Text.Json.Serialization.Metadata
 
                 if (fieldInfo.IsPublic)
                 {
-                    if (hasJsonInclude || Options.IncludeFields)
+                    if (hasJsonInclude || typeInfo.Options.IncludeFields)
                     {
-                        CacheMember(
+                        AddMember(
+                            typeInfo,
                             typeToConvert: fieldInfo.FieldType,
                             memberInfo: fieldInfo,
-                            ref propertyOrderSpecified,
-                            ref ignoredMembers,
-                            shouldCheckMembersForRequiredMemberAttribute);
+                            shouldCheckMembersForRequiredMemberAttribute,
+                            ref state);
                     }
                 }
                 else
@@ -174,14 +169,16 @@ namespace System.Text.Json.Serialization.Metadata
             }
         }
 
-        private void CacheMember(
+        [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
+        [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
+        private static void AddMember(
+            JsonTypeInfo typeInfo,
             Type typeToConvert,
             MemberInfo memberInfo,
-            ref bool propertyOrderSpecified,
-            ref Dictionary<string, JsonPropertyInfo>? ignoredMembers,
-            bool shouldCheckForRequiredKeyword)
+            bool shouldCheckForRequiredKeyword,
+            ref JsonTypeInfo.PropertyHierarchyResolutionState state)
         {
-            JsonPropertyInfo? jsonPropertyInfo = CreateProperty(typeToConvert, memberInfo, Options, shouldCheckForRequiredKeyword);
+            JsonPropertyInfo? jsonPropertyInfo = CreatePropertyInfo(typeInfo, typeToConvert, memberInfo, typeInfo.Options, shouldCheckForRequiredKeyword);
             if (jsonPropertyInfo == null)
             {
                 // ignored invalid property
@@ -189,16 +186,13 @@ namespace System.Text.Json.Serialization.Metadata
             }
 
             Debug.Assert(jsonPropertyInfo.Name != null);
-            Debug.Assert(PropertyCache != null);
-            CacheMember(jsonPropertyInfo, PropertyCache, ref ignoredMembers);
-            propertyOrderSpecified |= jsonPropertyInfo.Order != 0;
+            typeInfo.AddProperty(jsonPropertyInfo, ref state);
         }
 
-        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
-            Justification = "The ctor is marked as RequiresUnreferencedCode")]
-        [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
-            Justification = "The ctor is marked RequiresDynamicCode.")]
-        private JsonPropertyInfo? CreateProperty(
+        [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
+        [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
+        private static JsonPropertyInfo? CreatePropertyInfo(
+            JsonTypeInfo typeInfo,
             Type typeToConvert,
             MemberInfo memberInfo,
             JsonSerializerOptions options,
@@ -206,7 +200,7 @@ namespace System.Text.Json.Serialization.Metadata
         {
             JsonIgnoreCondition? ignoreCondition = memberInfo.GetCustomAttribute<JsonIgnoreAttribute>(inherit: false)?.Condition;
 
-            if (IsInvalidForSerialization(typeToConvert))
+            if (JsonTypeInfo.IsInvalidForSerialization(typeToConvert))
             {
                 if (ignoreCondition == JsonIgnoreCondition.Always)
                     return null;
@@ -226,7 +220,7 @@ namespace System.Text.Json.Serialization.Metadata
                 return null;
             }
 
-            JsonPropertyInfo jsonPropertyInfo = CreatePropertyUsingReflection(typeToConvert);
+            JsonPropertyInfo jsonPropertyInfo = typeInfo.CreatePropertyUsingReflection(typeToConvert);
             jsonPropertyInfo.InitializeUsingMemberReflection(memberInfo, customConverter, ignoreCondition, shouldCheckForRequiredKeyword);
             return jsonPropertyInfo;
         }
@@ -259,10 +253,10 @@ namespace System.Text.Json.Serialization.Metadata
                 ignoredMember.IsVirtual;
         }
 
-        internal override JsonParameterInfoValues[] GetParameterInfoValues()
+        private static void PopulateParameterInfoValues(JsonTypeInfo typeInfo)
         {
-            Debug.Assert(Converter.ConstructorInfo != null);
-            ParameterInfo[] parameters = Converter.ConstructorInfo.GetParameters();
+            Debug.Assert(typeInfo.Converter.ConstructorInfo != null);
+            ParameterInfo[] parameters = typeInfo.Converter.ConstructorInfo.GetParameters();
             int parameterCount = parameters.Length;
             JsonParameterInfoValues[] jsonParameters = new JsonParameterInfoValues[parameterCount];
 
@@ -273,8 +267,8 @@ namespace System.Text.Json.Serialization.Metadata
                 // Trimmed parameter names are reported as null in CoreCLR or "" in Mono.
                 if (string.IsNullOrEmpty(reflectionInfo.Name))
                 {
-                    Debug.Assert(Converter.ConstructorInfo.DeclaringType != null);
-                    ThrowHelper.ThrowNotSupportedException_ConstructorContainsNullParameterNames(Converter.ConstructorInfo.DeclaringType);
+                    Debug.Assert(typeInfo.Converter.ConstructorInfo.DeclaringType != null);
+                    ThrowHelper.ThrowNotSupportedException_ConstructorContainsNullParameterNames(typeInfo.Converter.ConstructorInfo.DeclaringType);
                 }
 
                 JsonParameterInfoValues jsonInfo = new()
@@ -289,7 +283,7 @@ namespace System.Text.Json.Serialization.Metadata
                 jsonParameters[i] = jsonInfo;
             }
 
-            return jsonParameters;
+            typeInfo.ParameterInfoValues = jsonParameters;
         }
     }
 }
index c90a545..ae19c96 100644 (file)
@@ -2,9 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
-using System.Text.Json.Reflection;
 using System.Threading;
 
 namespace System.Text.Json.Serialization.Metadata
@@ -85,25 +83,8 @@ namespace System.Text.Json.Serialization.Metadata
         [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
         private static JsonTypeInfo CreateJsonTypeInfo(Type type, JsonSerializerOptions options)
         {
-            JsonTypeInfo jsonTypeInfo;
             JsonConverter converter = GetConverterForType(type, options);
-
-            if (converter.TypeToConvert == type)
-            {
-                // For performance, avoid doing a reflection-based instantiation
-                // if the converter type matches that of the declared type.
-                jsonTypeInfo = converter.CreateReflectionJsonTypeInfo(options);
-            }
-            else
-            {
-                Type jsonTypeInfoType = typeof(ReflectionJsonTypeInfo<>).MakeGenericType(type);
-                jsonTypeInfo = (JsonTypeInfo)jsonTypeInfoType.CreateInstanceNoWrapExceptions(
-                    parameterTypes: new Type[] { typeof(JsonConverter), typeof(JsonSerializerOptions) },
-                    parameters: new object[] { converter, options })!;
-            }
-
-            Debug.Assert(jsonTypeInfo.Type == type);
-            return jsonTypeInfo;
+            return CreateTypeInfoCore(type, converter, options);
         }
 
         /// <summary>
index 9609a92..de712db 100644 (file)
@@ -19,7 +19,7 @@ namespace System.Text.Json.Serialization.Metadata
         /// <returns>Serialization metadata for the given type.</returns>
         /// <remarks>This API is for use by the output of the System.Text.Json source generator and should not be called directly.</remarks>
         public static JsonTypeInfo<TElement[]> CreateArrayInfo<TElement>(JsonSerializerOptions options, JsonCollectionInfoValues<TElement[]> collectionInfo)
-            => new SourceGenJsonTypeInfo<TElement[]>(
+            => CreateCore(
                 options,
                 collectionInfo,
                 () => new ArrayConverter<TElement[], TElement>());
@@ -37,7 +37,7 @@ namespace System.Text.Json.Serialization.Metadata
             JsonSerializerOptions options,
             JsonCollectionInfoValues<TCollection> collectionInfo)
             where TCollection : List<TElement>
-            => new SourceGenJsonTypeInfo<TCollection>(
+            => CreateCore(
                 options,
                 collectionInfo,
                 () => new ListOfTConverter<TCollection, TElement>());
@@ -57,7 +57,7 @@ namespace System.Text.Json.Serialization.Metadata
             JsonCollectionInfoValues<TCollection> collectionInfo)
             where TCollection : Dictionary<TKey, TValue>
             where TKey : notnull
-            => new SourceGenJsonTypeInfo<TCollection>(
+            => CreateCore(
                 options,
                 collectionInfo,
                 () => new DictionaryOfTKeyTValueConverter<TCollection, TKey, TValue>());
@@ -89,7 +89,7 @@ namespace System.Text.Json.Serialization.Metadata
                 ThrowHelper.ThrowArgumentNullException(nameof(createRangeFunc));
             }
 
-            return new SourceGenJsonTypeInfo<TCollection>(
+            return CreateCore(
                 options,
                 collectionInfo,
                 () => new ImmutableDictionaryOfTKeyTValueConverter<TCollection, TKey, TValue>(),
@@ -111,7 +111,7 @@ namespace System.Text.Json.Serialization.Metadata
             JsonCollectionInfoValues<TCollection> collectionInfo)
             where TCollection : IDictionary<TKey, TValue>
             where TKey : notnull
-            => new SourceGenJsonTypeInfo<TCollection>(
+            => CreateCore(
                 options,
                 collectionInfo,
                 () => new IDictionaryOfTKeyTValueConverter<TCollection, TKey, TValue>());
@@ -131,7 +131,7 @@ namespace System.Text.Json.Serialization.Metadata
             JsonCollectionInfoValues<TCollection> collectionInfo)
             where TCollection : IReadOnlyDictionary<TKey, TValue>
             where TKey : notnull
-            => new SourceGenJsonTypeInfo<TCollection>(
+            => CreateCore(
                 options,
                 collectionInfo,
                 () => new IReadOnlyDictionaryOfTKeyTValueConverter<TCollection, TKey, TValue>());
@@ -159,7 +159,7 @@ namespace System.Text.Json.Serialization.Metadata
                 ThrowHelper.ThrowArgumentNullException(nameof(createRangeFunc));
             }
 
-            return new SourceGenJsonTypeInfo<TCollection>(
+            return CreateCore(
                 options,
                 collectionInfo,
                 () => new ImmutableEnumerableOfTConverter<TCollection, TElement>(),
@@ -178,7 +178,7 @@ namespace System.Text.Json.Serialization.Metadata
             JsonSerializerOptions options,
             JsonCollectionInfoValues<TCollection> collectionInfo)
             where TCollection : IList
-            => new SourceGenJsonTypeInfo<TCollection>(
+            => CreateCore(
                 options,
                 collectionInfo,
                 () => new IListConverter<TCollection>());
@@ -196,7 +196,7 @@ namespace System.Text.Json.Serialization.Metadata
             JsonSerializerOptions options,
             JsonCollectionInfoValues<TCollection> collectionInfo)
             where TCollection : IList<TElement>
-            => new SourceGenJsonTypeInfo<TCollection>(
+            => CreateCore(
                 options,
                 collectionInfo,
                 () => new IListOfTConverter<TCollection, TElement>());
@@ -214,7 +214,7 @@ namespace System.Text.Json.Serialization.Metadata
             JsonSerializerOptions options,
             JsonCollectionInfoValues<TCollection> collectionInfo)
             where TCollection : ISet<TElement>
-            => new SourceGenJsonTypeInfo<TCollection>(
+            => CreateCore(
                 options,
                 collectionInfo,
                 () => new ISetOfTConverter<TCollection, TElement>());
@@ -232,7 +232,7 @@ namespace System.Text.Json.Serialization.Metadata
             JsonSerializerOptions options,
             JsonCollectionInfoValues<TCollection> collectionInfo)
             where TCollection : ICollection<TElement>
-            => new SourceGenJsonTypeInfo<TCollection>(
+            => CreateCore(
                 options,
                 collectionInfo,
                 () => new ICollectionOfTConverter<TCollection, TElement>());
@@ -250,7 +250,7 @@ namespace System.Text.Json.Serialization.Metadata
             JsonSerializerOptions options,
             JsonCollectionInfoValues<TCollection> collectionInfo)
             where TCollection : Stack<TElement>
-            => new SourceGenJsonTypeInfo<TCollection>(
+            => CreateCore(
                 options,
                 collectionInfo,
                 () => new StackOfTConverter<TCollection, TElement>());
@@ -268,7 +268,7 @@ namespace System.Text.Json.Serialization.Metadata
             JsonSerializerOptions options,
             JsonCollectionInfoValues<TCollection> collectionInfo)
             where TCollection : Queue<TElement>
-            => new SourceGenJsonTypeInfo<TCollection>(
+            => CreateCore(
                 options,
                 collectionInfo,
                 () => new QueueOfTConverter<TCollection, TElement>());
@@ -286,7 +286,7 @@ namespace System.Text.Json.Serialization.Metadata
             JsonSerializerOptions options,
             JsonCollectionInfoValues<TCollection> collectionInfo)
             where TCollection : ConcurrentStack<TElement>
-            => new SourceGenJsonTypeInfo<TCollection>(
+            => CreateCore(
                 options,
                 collectionInfo,
                 () => new ConcurrentStackOfTConverter<TCollection, TElement>());
@@ -304,7 +304,7 @@ namespace System.Text.Json.Serialization.Metadata
             JsonSerializerOptions options,
             JsonCollectionInfoValues<TCollection> collectionInfo)
             where TCollection : ConcurrentQueue<TElement>
-            => new SourceGenJsonTypeInfo<TCollection>(
+            => CreateCore(
                 options,
                 collectionInfo,
                 () => new ConcurrentQueueOfTConverter<TCollection, TElement>());
@@ -322,7 +322,7 @@ namespace System.Text.Json.Serialization.Metadata
             JsonSerializerOptions options,
             JsonCollectionInfoValues<TCollection> collectionInfo)
             where TCollection : IEnumerable<TElement>
-            => new SourceGenJsonTypeInfo<TCollection>(
+            => CreateCore(
                 options,
                 collectionInfo,
                 () => new IEnumerableOfTConverter<TCollection, TElement>());
@@ -340,7 +340,7 @@ namespace System.Text.Json.Serialization.Metadata
             JsonSerializerOptions options,
             JsonCollectionInfoValues<TCollection> collectionInfo)
             where TCollection : IAsyncEnumerable<TElement>
-            => new SourceGenJsonTypeInfo<TCollection>(
+            => CreateCore(
                 options,
                 collectionInfo,
                 () => new IAsyncEnumerableOfTConverter<TCollection, TElement>());
@@ -357,7 +357,7 @@ namespace System.Text.Json.Serialization.Metadata
             JsonSerializerOptions options,
             JsonCollectionInfoValues<TCollection> collectionInfo)
             where TCollection : IDictionary
-            => new SourceGenJsonTypeInfo<TCollection>(
+            => CreateCore(
                 options,
                 collectionInfo,
                 () => new IDictionaryConverter<TCollection>());
@@ -409,7 +409,7 @@ namespace System.Text.Json.Serialization.Metadata
                 ThrowHelper.ThrowArgumentNullException(nameof(addFunc));
             }
 
-            return new SourceGenJsonTypeInfo<TCollection>(
+            return CreateCore(
                 options,
                 collectionInfo,
                 () => new StackOrQueueConverter<TCollection>(),
@@ -429,7 +429,7 @@ namespace System.Text.Json.Serialization.Metadata
             JsonSerializerOptions options,
             JsonCollectionInfoValues<TCollection> collectionInfo)
             where TCollection : IEnumerable
-            => new SourceGenJsonTypeInfo<TCollection>(
+            => CreateCore(
                 options,
                 collectionInfo,
                 () => new IEnumerableConverter<TCollection>());
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Helpers.cs
new file mode 100644 (file)
index 0000000..564c6aa
--- /dev/null
@@ -0,0 +1,173 @@
+// 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.Diagnostics;
+using System.Reflection;
+using System.Text.Json.Serialization.Converters;
+
+namespace System.Text.Json.Serialization.Metadata
+{
+    public static partial class JsonMetadataServices
+    {
+        /// <summary>
+        /// Creates serialization metadata for a type using a simple converter.
+        /// </summary>
+        private static JsonTypeInfo<T> CreateCore<T>(JsonConverter converter, JsonSerializerOptions options)
+        {
+            JsonTypeInfo<T> typeInfo = new JsonTypeInfo<T>(converter, options);
+            typeInfo.PopulatePolymorphismMetadata();
+            typeInfo.MapInterfaceTypesToCallbacks();
+
+            // Plug in any converter configuration -- should be run last.
+            converter.ConfigureJsonTypeInfo(typeInfo, options);
+            return typeInfo;
+        }
+
+        /// <summary>
+        /// Creates serialization metadata for an object.
+        /// </summary>
+        private static JsonTypeInfo<T> CreateCore<T>(JsonSerializerOptions options, JsonObjectInfoValues<T> objectInfo)
+        {
+            JsonConverter<T> converter = GetConverter(objectInfo);
+            JsonTypeInfo<T> typeInfo = new JsonTypeInfo<T>(converter, options);
+            if (objectInfo.ObjectWithParameterizedConstructorCreator != null)
+            {
+                typeInfo.CreateObjectWithArgs = objectInfo.ObjectWithParameterizedConstructorCreator;
+                PopulateParameterInfoValues(typeInfo, objectInfo.ConstructorParameterMetadataInitializer);
+            }
+            else
+            {
+                typeInfo.SetCreateObjectIfCompatible(objectInfo.ObjectCreator);
+                typeInfo.CreateObjectForExtensionDataProperty = ((JsonTypeInfo)typeInfo).CreateObject;
+            }
+
+            PopulateProperties(typeInfo, objectInfo.PropertyMetadataInitializer);
+            typeInfo.SerializeHandler = objectInfo.SerializeHandler;
+            typeInfo.NumberHandling = objectInfo.NumberHandling;
+            typeInfo.PopulatePolymorphismMetadata();
+            typeInfo.MapInterfaceTypesToCallbacks();
+
+            // Plug in any converter configuration -- should be run last.
+            converter.ConfigureJsonTypeInfo(typeInfo, options);
+            return typeInfo;
+        }
+
+        /// <summary>
+        /// Creates serialization metadata for a collection.
+        /// </summary>
+        private static JsonTypeInfo<T> CreateCore<T>(
+            JsonSerializerOptions options,
+            JsonCollectionInfoValues<T> collectionInfo,
+            Func<JsonConverter<T>> converterCreator,
+            object? createObjectWithArgs = null,
+            object? addFunc = null)
+        {
+            JsonConverter<T> converter = new JsonMetadataServicesConverter<T>(converterCreator());
+            JsonTypeInfo<T> typeInfo = new JsonTypeInfo<T>(converter, options);
+            if (collectionInfo is null)
+            {
+                ThrowHelper.ThrowArgumentNullException(nameof(collectionInfo));
+            }
+
+            typeInfo.KeyTypeInfo = collectionInfo.KeyInfo;
+            typeInfo.ElementTypeInfo = collectionInfo.ElementInfo;
+            Debug.Assert(typeInfo.Kind != JsonTypeInfoKind.None);
+            typeInfo.NumberHandling = collectionInfo.NumberHandling;
+            typeInfo.SerializeHandler = collectionInfo.SerializeHandler;
+            typeInfo.CreateObjectWithArgs = createObjectWithArgs;
+            typeInfo.AddMethodDelegate = addFunc;
+            typeInfo.SetCreateObjectIfCompatible(collectionInfo.ObjectCreator);
+            typeInfo.PopulatePolymorphismMetadata();
+            typeInfo.MapInterfaceTypesToCallbacks();
+
+            // Plug in any converter configuration -- should be run last.
+            converter.ConfigureJsonTypeInfo(typeInfo, options);
+            return typeInfo;
+        }
+
+        private static JsonMetadataServicesConverter<T> GetConverter<T>(JsonObjectInfoValues<T> objectInfo)
+        {
+#pragma warning disable CS8714
+            // The type cannot be used as type parameter in the generic type or method.
+            // Nullability of type argument doesn't match 'notnull' constraint.
+            if (objectInfo.ObjectWithParameterizedConstructorCreator != null)
+            {
+                return new JsonMetadataServicesConverter<T>(
+                    () => new LargeObjectWithParameterizedConstructorConverter<T>(),
+                    ConverterStrategy.Object);
+            }
+            else
+            {
+                return new JsonMetadataServicesConverter<T>(() => new ObjectDefaultConverter<T>(), ConverterStrategy.Object);
+            }
+#pragma warning restore CS8714
+        }
+
+        private static void PopulateParameterInfoValues(JsonTypeInfo typeInfo, Func<JsonParameterInfoValues[]?>? paramFactory)
+        {
+            Debug.Assert(typeInfo.Kind is JsonTypeInfoKind.Object);
+            Debug.Assert(!typeInfo.IsReadOnly);
+
+            if (paramFactory?.Invoke() is JsonParameterInfoValues[] array)
+            {
+                typeInfo.ParameterInfoValues = array;
+            }
+            else
+            {
+                typeInfo.MetadataSerializationNotSupported = true;
+            }
+        }
+
+        private static void PopulateProperties(JsonTypeInfo typeInfo, Func<JsonSerializerContext, JsonPropertyInfo[]?>? propInitFunc)
+        {
+            Debug.Assert(typeInfo.Kind is JsonTypeInfoKind.Object);
+            Debug.Assert(!typeInfo.IsReadOnly);
+
+            JsonSerializerContext? context = typeInfo.Options.TypeInfoResolver as JsonSerializerContext;
+            if (propInitFunc?.Invoke(context!) is not JsonPropertyInfo[] properties)
+            {
+                if (typeInfo.Type == JsonTypeInfo.ObjectType)
+                {
+                    return;
+                }
+
+                if (typeInfo.Converter.ElementType != null)
+                {
+                    // Nullable<> or F# optional converter strategy is set to element strategy
+                    return;
+                }
+
+                typeInfo.MetadataSerializationNotSupported = true;
+                return;
+            }
+
+            // TODO update the source generator so that all property
+            // hierarchy resolution is happening at compile time.
+            JsonTypeInfo.PropertyHierarchyResolutionState state = new();
+
+            foreach (JsonPropertyInfo jsonPropertyInfo in properties)
+            {
+                if (!jsonPropertyInfo.SrcGen_IsPublic)
+                {
+                    if (jsonPropertyInfo.SrcGen_HasJsonInclude)
+                    {
+                        Debug.Assert(jsonPropertyInfo.MemberName != null, "MemberName is not set by source gen");
+                        ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(jsonPropertyInfo.MemberName, jsonPropertyInfo.DeclaringType);
+                    }
+
+                    continue;
+                }
+
+                if (jsonPropertyInfo.MemberType == MemberTypes.Field && !jsonPropertyInfo.SrcGen_HasJsonInclude && !typeInfo.Options.IncludeFields)
+                {
+                    continue;
+                }
+
+                typeInfo.AddProperty(jsonPropertyInfo, ref state);
+            }
+
+            // NB we don't need to sort source gen properties here since they were already sorted at compile time.
+        }
+    }
+}
index bdaec17..7a39f19 100644 (file)
@@ -71,7 +71,7 @@ namespace System.Text.Json.Serialization.Metadata
                 ThrowHelper.ThrowArgumentNullException(nameof(objectInfo));
             }
 
-            return new SourceGenJsonTypeInfo<T>(options, objectInfo);
+            return CreateCore(options, objectInfo);
         }
 
         /// <summary>
@@ -91,7 +91,7 @@ namespace System.Text.Json.Serialization.Metadata
                 ThrowHelper.ThrowArgumentNullException(nameof(converter));
             }
 
-            JsonTypeInfo<T> info = new SourceGenJsonTypeInfo<T>(converter, options);
+            JsonTypeInfo<T> info = CreateCore<T>(converter, options);
             return info;
         }
     }
index a73e1c2..af5d97b 100644 (file)
@@ -289,13 +289,23 @@ namespace System.Text.Json.Serialization.Metadata
 
         internal void Configure()
         {
-            if (!IsForTypeInfo)
+            Debug.Assert(ParentTypeInfo != null);
+
+            if (IsIgnored)
             {
-                CacheNameAsUtf8BytesAndEscapedNameSection();
+                // Avoid configuring JsonIgnore.Always properties
+                // to avoid failing on potentially unsupported types.
+                CanSerialize = false;
+                CanDeserialize = false;
+            }
+            else
+            {
+                _jsonTypeInfo ??= Options.GetTypeInfoInternal(PropertyType, ensureConfigured: false);
+                DetermineEffectiveConverter(_jsonTypeInfo);
+                DetermineNumberHandlingForProperty();
+                DetermineSerializationCapabilities();
+                DetermineIgnoreCondition();
             }
-
-            _jsonTypeInfo ??= Options.GetTypeInfoInternal(PropertyType, ensureConfigured: false);
-            DetermineEffectiveConverter(_jsonTypeInfo);
 
             if (IsForTypeInfo)
             {
@@ -303,9 +313,7 @@ namespace System.Text.Json.Serialization.Metadata
             }
             else
             {
-                DetermineNumberHandlingForProperty();
-                DetermineIgnoreCondition();
-                DetermineSerializationCapabilities();
+                CacheNameAsUtf8BytesAndEscapedNameSection();
             }
 
             if (IsRequired)
@@ -404,7 +412,6 @@ namespace System.Text.Json.Serialization.Metadata
         private void DetermineSerializationCapabilities()
         {
             Debug.Assert(EffectiveConverter != null, "Must have calculated the effective converter.");
-
             CanSerialize = HasGetter;
             CanDeserialize = HasSetter;
 
@@ -787,7 +794,6 @@ namespace System.Text.Json.Serialization.Metadata
 
         internal Type DeclaringType { get; }
 
-        [AllowNull]
         internal JsonTypeInfo JsonTypeInfo
         {
             get
@@ -799,13 +805,14 @@ namespace System.Text.Json.Serialization.Metadata
             }
             set
             {
-                // This could potentially be double initialized
-                Debug.Assert(_jsonTypeInfo == null || _jsonTypeInfo == value);
                 _jsonTypeInfo = value;
             }
         }
 
-        internal bool IsIgnored => _ignoreCondition == JsonIgnoreCondition.Always;
+        /// <summary>
+        /// Property was marked JsonIgnoreCondition.Always and also hasn't been configured by the user.
+        /// </summary>
+        internal bool IsIgnored => _ignoreCondition is JsonIgnoreCondition.Always && Get is null && Set is null;
 
         /// <summary>
         /// Reflects the value of <see cref="HasGetter"/> combined with any additional global ignore policies.
index 58fe2bb..3d061c9 100644 (file)
@@ -37,7 +37,7 @@ namespace System.Text.Json.Serialization.Metadata
         internal JsonPropertyDictionary<JsonParameterInfo>? ParameterCache { get; private set; }
 
         // All of the serializable properties on a POCO (except the optional extension property) keyed on property name.
-        internal JsonPropertyDictionary<JsonPropertyInfo>? PropertyCache { get; private protected set; }
+        internal JsonPropertyDictionary<JsonPropertyInfo>? PropertyCache { get; private set; }
 
         // Fast cache of constructor parameters by first JSON ordering; may not contain all parameters. Accessed before ParameterCache.
         // Use an array (instead of List<T>) for highest performance.
@@ -47,10 +47,6 @@ namespace System.Text.Json.Serialization.Metadata
         // Use an array (instead of List<T>) for highest performance.
         private volatile PropertyRef[]? _propertyRefsSorted;
 
-        internal Func<JsonSerializerContext, JsonPropertyInfo[]>? PropInitFunc;
-
-        internal Func<JsonParameterInfoValues[]>? CtorParamInitFunc;
-
         [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
         [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
         internal JsonPropertyInfo CreatePropertyUsingReflection(Type propertyType)
index 034864c..90f433f 100644 (file)
@@ -8,6 +8,7 @@ using System.IO;
 using System.Runtime.CompilerServices;
 using System.Runtime.ExceptionServices;
 using System.Text.Json.Reflection;
+using System.Text.Json.Serialization.Converters;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -25,8 +26,6 @@ namespace System.Text.Json.Serialization.Metadata
 
         internal delegate T ParameterizedConstructorDelegate<T, TArg0, TArg1, TArg2, TArg3>(TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3);
 
-        private JsonPropertyInfoList? _properties;
-
         /// <summary>
         /// Indices of required properties.
         /// </summary>
@@ -37,6 +36,17 @@ namespace System.Text.Json.Serialization.Metadata
         private Action<object>? _onDeserializing;
         private Action<object>? _onDeserialized;
 
+        internal JsonTypeInfo(Type type, JsonConverter converter, JsonSerializerOptions options)
+        {
+            Type = type;
+            Options = options;
+            Converter = converter;
+            Kind = GetTypeInfoKind(type, converter);
+            PropertyInfoForTypeInfo = CreatePropertyInfoForTypeInfo();
+            ElementType = converter.ElementType;
+            KeyType = converter.KeyType;
+        }
+
         /// <summary>
         /// Gets or sets a parameterless factory to be used on deserialization.
         /// </summary>
@@ -202,17 +212,16 @@ namespace System.Text.Json.Serialization.Metadata
         /// It is required that added <see cref="JsonPropertyInfo"/> entries are unique up to <see cref="JsonPropertyInfo.Name"/>,
         /// however this will only be validated on serialization, once the metadata instance gets locked for further modification.
         /// </remarks>
-        public IList<JsonPropertyInfo> Properties
+        public IList<JsonPropertyInfo> Properties => _properties ??= new(this);
+        private JsonPropertyInfoList? _properties;
+
+        internal void SortProperties()
         {
-            get
-            {
-                if (_properties == null)
-                {
-                    PopulatePropertyList();
-                }
+            Debug.Assert(!IsConfigured);
+            Debug.Assert(_properties != null && _properties.Count > 0);
 
-                return _properties;
-            }
+            _properties.SortProperties();
+            PropertyCache?.List.StableSortByKey(static propInfo => propInfo.Value.Order);
         }
 
         /// <summary>
@@ -294,7 +303,7 @@ namespace System.Text.Json.Serialization.Metadata
 
         // 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
-        internal bool MetadataSerializationNotSupported { get; private protected set; }
+        internal bool MetadataSerializationNotSupported { get; set; }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal void ValidateCanBeUsedForMetadataSerialization()
@@ -513,38 +522,6 @@ namespace System.Text.Json.Serialization.Metadata
 
         internal JsonUnmappedMemberHandling EffectiveUnmappedMemberHandling { get; private set; }
 
-        internal JsonTypeInfo(Type type, JsonConverter converter, JsonSerializerOptions options)
-        {
-            Type = type;
-            Options = options;
-            Converter = converter;
-            PropertyInfoForTypeInfo = CreatePropertyInfoForTypeInfo();
-            ElementType = converter.ElementType;
-
-            switch (converter.ConverterStrategy)
-            {
-                case ConverterStrategy.Dictionary:
-                    {
-                        KeyType = converter.KeyType;
-                    }
-                    break;
-                case ConverterStrategy.Object:
-                case ConverterStrategy.Enumerable:
-                case ConverterStrategy.Value:
-                    break;
-                case ConverterStrategy.None:
-                    {
-                        ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type);
-                    }
-                    break;
-                default:
-                    Debug.Fail($"Unexpected class type: {converter.ConverterStrategy}");
-                    throw new InvalidOperationException();
-            }
-
-            Kind = GetTypeInfoKind(type, converter.ConverterStrategy);
-        }
-
         internal void VerifyMutable()
         {
             if (IsReadOnly)
@@ -602,21 +579,19 @@ namespace System.Text.Json.Serialization.Metadata
                 Options.MakeReadOnly();
             }
 
-            PropertyInfoForTypeInfo.EnsureChildOf(this);
             PropertyInfoForTypeInfo.EnsureConfigured();
 
             CanUseSerializeHandler &= Options.CanUseFastPathSerializationLogic;
 
-            JsonConverter converter = Converter;
             Debug.Assert(PropertyInfoForTypeInfo.EffectiveConverter.ConverterStrategy == Converter.ConverterStrategy,
                 $"ConverterStrategy from PropertyInfoForTypeInfo.EffectiveConverter.ConverterStrategy ({PropertyInfoForTypeInfo.EffectiveConverter.ConverterStrategy}) does not match converter's ({Converter.ConverterStrategy})");
             if (Kind == JsonTypeInfoKind.Object)
             {
-                InitializePropertyCache();
+                ConfigureProperties();
 
-                if (converter.ConstructorIsParameterized)
+                if (Converter.ConstructorIsParameterized)
                 {
-                    InitializeConstructorParameters(GetParameterInfoValues(), sourceGenMode: Options.TypeInfoResolver is JsonSerializerContext);
+                    ConfigureConstructorParameters();
                 }
             }
 
@@ -651,10 +626,9 @@ namespace System.Text.Json.Serialization.Metadata
             if (propCacheInitialized)
             {
                 sb.AppendLine("  Properties: {");
-                foreach (var property in PropertyCache!.List)
+                foreach (JsonPropertyInfo pi in PropertyCache!.Values)
                 {
-                    JsonPropertyInfo pi = property.Value;
-                    sb.AppendLine($"    {property.Key}:");
+                    sb.AppendLine($"    {pi.Name}:");
                     sb.AppendLine($"{pi.GetDebugInfo(indent: 6)},");
                 }
 
@@ -666,8 +640,6 @@ namespace System.Text.Json.Serialization.Metadata
         }
 #endif
 
-        internal virtual void LateAddProperties() { }
-
         /// <summary>
         /// Creates a blank <see cref="JsonTypeInfo{T}"/> instance.
         /// </summary>
@@ -695,7 +667,7 @@ namespace System.Text.Json.Serialization.Metadata
             }
 
             JsonConverter converter = DefaultJsonTypeInfoResolver.GetConverterForType(typeof(T), options, resolveJsonConverterAttribute: false);
-            return new CustomJsonTypeInfo<T>(converter, options);
+            return new JsonTypeInfo<T>(converter, options);
         }
 
         /// <summary>
@@ -735,18 +707,25 @@ namespace System.Text.Json.Serialization.Metadata
                 ThrowHelper.ThrowArgumentException_CannotSerializeInvalidType(nameof(type), type, null, null);
             }
 
-            JsonTypeInfo jsonTypeInfo;
             JsonConverter converter = DefaultJsonTypeInfoResolver.GetConverterForType(type, options, resolveJsonConverterAttribute: false);
+            return CreateJsonTypeInfo(type, converter, options);
+        }
+
+        [RequiresUnreferencedCode(MetadataFactoryRequiresUnreferencedCode)]
+        [RequiresDynamicCode(MetadataFactoryRequiresUnreferencedCode)]
+        internal static JsonTypeInfo CreateJsonTypeInfo(Type type, JsonConverter converter, JsonSerializerOptions options)
+        {
+            JsonTypeInfo jsonTypeInfo;
 
             if (converter.TypeToConvert == type)
             {
                 // For performance, avoid doing a reflection-based instantiation
                 // if the converter type matches that of the declared type.
-                jsonTypeInfo = converter.CreateCustomJsonTypeInfo(options);
+                jsonTypeInfo = converter.CreateJsonTypeInfo(options);
             }
             else
             {
-                Type jsonTypeInfoType = typeof(CustomJsonTypeInfo<>).MakeGenericType(type);
+                Type jsonTypeInfoType = typeof(JsonTypeInfo<>).MakeGenericType(type);
                 jsonTypeInfo = (JsonTypeInfo)jsonTypeInfoType.CreateInstanceNoWrapExceptions(
                     parameterTypes: new Type[] { typeof(JsonConverter), typeof(JsonSerializerOptions) },
                     parameters: new object[] { converter, options })!;
@@ -791,30 +770,7 @@ namespace System.Text.Json.Serialization.Metadata
             return propertyInfo;
         }
 
-        [MemberNotNull(nameof(_properties))]
-        private void PopulatePropertyList()
-        {
-            Debug.Assert(!Monitor.IsEntered(_configureLock), "should not be invoked from Configure");
-
-            if (!_isConfigured)
-            {
-                // For mutable instances we need to synchronize access
-                // with Configure() calls, otherwise we risk corrupting property state.
-                lock (_configureLock)
-                {
-                    if (!_isConfigured)
-                    {
-                        LateAddProperties();
-                        _properties = new(this);
-                        return;
-                    }
-                }
-            }
-
-            _properties = new(this);
-        }
-
-        internal abstract JsonParameterInfoValues[] GetParameterInfoValues();
+        internal JsonParameterInfoValues[]? ParameterInfoValues { get; set; }
 
         // Untyped, root-level serialization methods
         internal abstract void SerializeAsObject(Utf8JsonWriter writer, object? rootValue, bool isInvokedByPolymorphicConverter = false);
@@ -827,38 +783,32 @@ namespace System.Text.Json.Serialization.Metadata
         internal abstract object? DeserializeAsObject(Stream utf8Json);
         internal abstract IAsyncEnumerable<object?> DeserializeAsyncEnumerableAsObject(Stream utf8Json, CancellationToken cancellationToken);
 
-        internal void CacheMember(JsonPropertyInfo jsonPropertyInfo, JsonPropertyDictionary<JsonPropertyInfo> propertyCache, ref Dictionary<string, JsonPropertyInfo>? ignoredMembers)
+        /// <summary>
+        /// Used by the built-in resolvers to add property metadata applying conflict resolution.
+        /// </summary>
+        internal void AddProperty(JsonPropertyInfo jsonPropertyInfo, ref PropertyHierarchyResolutionState state)
         {
             Debug.Assert(jsonPropertyInfo.MemberName != null, "MemberName can be null in custom JsonPropertyInfo instances and should never be passed in this method");
             string memberName = jsonPropertyInfo.MemberName;
 
-            jsonPropertyInfo.EnsureChildOf(this);
+            JsonPropertyInfoList properties = _properties ??= new(this);
 
-            if (jsonPropertyInfo.IsExtensionData)
+            if (state.AddedProperties.TryAdd(jsonPropertyInfo.Name, (jsonPropertyInfo, properties.Count)))
             {
-                if (UnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow)
-                {
-                    ThrowHelper.ThrowInvalidOperationException_ExtensionDataConflictsWithUnmappedMemberHandling(Type, jsonPropertyInfo);
-                }
-
-                if (ExtensionDataProperty != null)
-                {
-                    ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(Type, typeof(JsonExtensionDataAttribute));
-                }
-
-                ExtensionDataProperty = jsonPropertyInfo;
-                return;
+                properties.Add(jsonPropertyInfo);
+                state.IsPropertyOrderSpecified |= jsonPropertyInfo.Order != 0;
             }
-
-            // The JsonPropertyNameAttribute or naming policy resulted in a collision.
-            if (!propertyCache.TryAdd(jsonPropertyInfo.Name, jsonPropertyInfo))
+            else
             {
-                JsonPropertyInfo other = propertyCache[jsonPropertyInfo.Name]!;
+                // The JsonPropertyNameAttribute or naming policy resulted in a collision.
+                (JsonPropertyInfo other, int index) = state.AddedProperties[jsonPropertyInfo.Name];
 
                 if (other.IsIgnored)
                 {
                     // Overwrite previously cached property since it has [JsonIgnore].
-                    propertyCache[jsonPropertyInfo.Name] = jsonPropertyInfo;
+                    state.AddedProperties[jsonPropertyInfo.Name] = (jsonPropertyInfo, index);
+                    properties[index] = jsonPropertyInfo;
+                    state.IsPropertyOrderSpecified |= jsonPropertyInfo.Order != 0;
                 }
                 else
                 {
@@ -874,7 +824,7 @@ namespace System.Text.Json.Serialization.Metadata
                             other.MemberName == memberName ||
                             // Was a property with the same CLR name ignored? That property hid the current property,
                             // thus, if it was ignored, the current property should be ignored too.
-                            ignoredMembers?.ContainsKey(memberName) == true;
+                            state.IgnoredProperties?.ContainsKey(memberName) == true;
                     }
                     else
                     {
@@ -899,10 +849,18 @@ namespace System.Text.Json.Serialization.Metadata
 
             if (jsonPropertyInfo.IsIgnored)
             {
-                (ignoredMembers ??= new()).Add(memberName, jsonPropertyInfo);
+                (state.IgnoredProperties ??= new()).Add(memberName, jsonPropertyInfo);
             }
         }
 
+        internal ref struct PropertyHierarchyResolutionState
+        {
+            public PropertyHierarchyResolutionState() { }
+            public Dictionary<string, (JsonPropertyInfo, int index)> AddedProperties = new();
+            public Dictionary<string, JsonPropertyInfo>? IgnoredProperties;
+            public bool IsPropertyOrderSpecified;
+        }
+
         private sealed class ParameterLookupKey
         {
             public ParameterLookupKey(string name, Type type)
@@ -939,85 +897,68 @@ namespace System.Text.Json.Serialization.Metadata
             public JsonPropertyInfo JsonPropertyInfo { get; }
         }
 
-        internal void InitializePropertyCache()
+        internal void ConfigureProperties()
         {
             Debug.Assert(Kind == JsonTypeInfoKind.Object);
+            Debug.Assert(PropertyCache is null);
+            Debug.Assert(ExtensionDataProperty is null);
+
+            IList<JsonPropertyInfo> properties = (IList<JsonPropertyInfo>?)_properties ?? Array.Empty<JsonPropertyInfo>();
+
+            JsonPropertyDictionary<JsonPropertyInfo> propertyCache = CreatePropertyCache(capacity: properties.Count);
+            int numberOfRequiredProperties = 0;
+            bool arePropertiesSorted = true;
+            int previousPropertyOrder = int.MinValue;
 
-            if (_properties != null)
+            foreach (JsonPropertyInfo property in properties)
             {
-                // Properties have been exported to a metadata resolver,
-                // invalidate the property cache and build from scratch
+                Debug.Assert(property.ParentTypeInfo == this);
 
-                ExtensionDataProperty = null;
-                if (PropertyCache is null)
+                if (property.IsExtensionData)
                 {
-                    PropertyCache = CreatePropertyCache(capacity: _properties.Count);
+                    if (UnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow)
+                    {
+                        ThrowHelper.ThrowInvalidOperationException_ExtensionDataConflictsWithUnmappedMemberHandling(Type, property);
+                    }
+
+                    if (ExtensionDataProperty != null)
+                    {
+                        ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(Type, typeof(JsonExtensionDataAttribute));
+                    }
+
+                    ExtensionDataProperty = property;
                 }
                 else
                 {
-                    PropertyCache.Clear();
-                }
+                    if (property.IsRequired)
+                    {
+                        property.RequiredPropertyIndex = numberOfRequiredProperties++;
+                    }
 
-                bool isOrderSpecified = false;
-                foreach (JsonPropertyInfo property in _properties)
-                {
-                    if (property.IsExtensionData)
+                    if (arePropertiesSorted)
                     {
-                        if (UnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow)
-                        {
-                            ThrowHelper.ThrowInvalidOperationException_ExtensionDataConflictsWithUnmappedMemberHandling(Type, property);
-                        }
-
-                        if (ExtensionDataProperty != null)
-                        {
-                            ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(Type, typeof(JsonExtensionDataAttribute));
-                        }
-
-                        ExtensionDataProperty = property;
-                        continue;
+                        arePropertiesSorted = previousPropertyOrder <= property.Order;
+                        previousPropertyOrder = property.Order;
                     }
 
-                    if (!PropertyCache.TryAddValue(property.Name, property))
+                    if (!propertyCache.TryAddValue(property.Name, property))
                     {
                         ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, property.Name);
                     }
-
-                    isOrderSpecified |= property.Order != 0;
                 }
 
-                if (isOrderSpecified)
-                {
-                    PropertyCache.List.StableSortByKey(static prop => prop.Value.Order);
-                }
-            }
-            else
-            {
-                // Resolver didn't modify any properties, create the property cache from scratch.
-                LateAddProperties();
-                PropertyCache ??= CreatePropertyCache(capacity: 0);
+                property.EnsureConfigured();
             }
 
-            if (ExtensionDataProperty != null)
-            {
-                ExtensionDataProperty.EnsureChildOf(this);
-                ExtensionDataProperty.EnsureConfigured();
-            }
+            NumberOfRequiredProperties = numberOfRequiredProperties;
+            PropertyCache = propertyCache;
 
-            int numberOfRequiredProperties = 0;
-            foreach (KeyValuePair<string, JsonPropertyInfo> jsonPropertyInfoKv in PropertyCache.List)
+            if (!arePropertiesSorted)
             {
-                JsonPropertyInfo jsonPropertyInfo = jsonPropertyInfoKv.Value;
-
-                if (jsonPropertyInfo.IsRequired)
-                {
-                    jsonPropertyInfo.RequiredPropertyIndex = numberOfRequiredProperties++;
-                }
-
-                jsonPropertyInfo.EnsureChildOf(this);
-                jsonPropertyInfo.EnsureConfigured();
+                // Properties have been configured by the user and require sorting.
+                SortProperties();
             }
 
-            NumberOfRequiredProperties = numberOfRequiredProperties;
             // Override global UnmappedMemberHandling configuration
             // if type specifies an extension data property.
             EffectiveUnmappedMemberHandling = UnmappedMemberHandling ??
@@ -1026,10 +967,15 @@ namespace System.Text.Json.Serialization.Metadata
                     : JsonUnmappedMemberHandling.Skip);
         }
 
-        internal void InitializeConstructorParameters(JsonParameterInfoValues[] jsonParameters, bool sourceGenMode = false)
+        internal void ConfigureConstructorParameters()
         {
-            Debug.Assert(ParameterCache is null);
             Debug.Assert(Kind == JsonTypeInfoKind.Object);
+            Debug.Assert(Converter.ConstructorIsParameterized);
+            Debug.Assert(PropertyCache is not null);
+            Debug.Assert(ParameterCache is null);
+
+            JsonParameterInfoValues[] jsonParameters = ParameterInfoValues ?? Array.Empty<JsonParameterInfoValues>();
+            bool sourceGenMode = Options.TypeInfoResolver is JsonSerializerContext;
 
             var parameterCache = new JsonPropertyDictionary<JsonParameterInfo>(Options.PropertyNameCaseInsensitive, jsonParameters.Length);
 
@@ -1038,7 +984,7 @@ namespace System.Text.Json.Serialization.Metadata
             // record types or anonymous types are used.
             // The property name key does not use [JsonPropertyName] or PropertyNamingPolicy since we only bind
             // the parameter name to the object property name and do not use the JSON version of the name here.
-            var nameLookup = new Dictionary<ParameterLookupKey, ParameterLookupValue>(PropertyCache!.Count);
+            var nameLookup = new Dictionary<ParameterLookupKey, ParameterLookupValue>(PropertyCache.Count);
 
             foreach (KeyValuePair<string, JsonPropertyInfo> kvp in PropertyCache.List)
             {
@@ -1078,7 +1024,7 @@ namespace System.Text.Json.Serialization.Metadata
                     JsonParameterInfo jsonParameterInfo = CreateConstructorParameter(parameterInfo, jsonPropertyInfo, sourceGenMode, Options);
                     parameterCache.Add(jsonPropertyInfo.Name, jsonParameterInfo);
                 }
-                // It is invalid for the extension data property to bind with a constructor argument.
+                // It is invalid for the extension data property to bind to a constructor argument.
                 else if (ExtensionDataProperty != null &&
                     StringComparer.OrdinalIgnoreCase.Equals(paramToCheck.Name, ExtensionDataProperty.Name))
                 {
@@ -1104,6 +1050,60 @@ namespace System.Text.Json.Serialization.Metadata
             return type == typeof(void) || type.IsPointer || type.IsByRef || IsByRefLike(type) || type.ContainsGenericParameters;
         }
 
+        internal void PopulatePolymorphismMetadata()
+        {
+            Debug.Assert(!IsReadOnly);
+
+            JsonPolymorphismOptions? options = JsonPolymorphismOptions.CreateFromAttributeDeclarations(Type);
+            if (options != null)
+            {
+                options.DeclaringTypeInfo = this;
+                _polymorphismOptions = options;
+            }
+        }
+
+        internal void MapInterfaceTypesToCallbacks()
+        {
+            Debug.Assert(!IsReadOnly);
+
+            // Callbacks currently only supported in object kinds
+            // TODO: extend to collections/dictionaries
+            if (Kind == JsonTypeInfoKind.Object)
+            {
+                if (typeof(IJsonOnSerializing).IsAssignableFrom(Type))
+                {
+                    OnSerializing = static obj => ((IJsonOnSerializing)obj).OnSerializing();
+                }
+
+                if (typeof(IJsonOnSerialized).IsAssignableFrom(Type))
+                {
+                    OnSerialized = static obj => ((IJsonOnSerialized)obj).OnSerialized();
+                }
+
+                if (typeof(IJsonOnDeserializing).IsAssignableFrom(Type))
+                {
+                    OnDeserializing = static obj => ((IJsonOnDeserializing)obj).OnDeserializing();
+                }
+
+                if (typeof(IJsonOnDeserialized).IsAssignableFrom(Type))
+                {
+                    OnDeserialized = static obj => ((IJsonOnDeserialized)obj).OnDeserialized();
+                }
+            }
+        }
+
+        internal void SetCreateObjectIfCompatible(Delegate? createObject)
+        {
+            Debug.Assert(!IsReadOnly);
+
+            // Guard against the reflection resolver/source generator attempting to pass
+            // a CreateObject delegate to converters/metadata that do not support it.
+            if (Converter.SupportsCreateObjectDelegate && !Converter.ConstructorIsParameterized)
+            {
+                SetCreateObject(createObject);
+            }
+        }
+
         private static bool IsByRefLike(Type type)
         {
 #if NETCOREAPP
@@ -1160,21 +1160,29 @@ namespace System.Text.Json.Serialization.Metadata
             return jsonParameterInfo;
         }
 
-        private static JsonTypeInfoKind GetTypeInfoKind(Type type, ConverterStrategy converterStrategy)
+        private static JsonTypeInfoKind GetTypeInfoKind(Type type, JsonConverter converter)
         {
-            // System.Object is polymorphic and will not respect Properties
-            if (type == typeof(object))
+            if (type == typeof(object) && converter.CanBePolymorphic)
             {
+                // System.Object is polymorphic and will not respect Properties
+                Debug.Assert(converter is ObjectConverter);
                 return JsonTypeInfoKind.None;
             }
 
-            return converterStrategy switch
+            switch (converter.ConverterStrategy)
             {
-                ConverterStrategy.Object => JsonTypeInfoKind.Object,
-                ConverterStrategy.Enumerable => JsonTypeInfoKind.Enumerable,
-                ConverterStrategy.Dictionary => JsonTypeInfoKind.Dictionary,
-                _ => JsonTypeInfoKind.None
-            };
+                case ConverterStrategy.Value: return JsonTypeInfoKind.None;
+                case ConverterStrategy.Object: return JsonTypeInfoKind.Object;
+                case ConverterStrategy.Enumerable: return JsonTypeInfoKind.Enumerable;
+                case ConverterStrategy.Dictionary: return JsonTypeInfoKind.Dictionary;
+                case ConverterStrategy.None:
+                    Debug.Assert(converter is JsonConverterFactory);
+                    ThrowHelper.ThrowNotSupportedException_SerializationNotSupported(type);
+                    return default;
+                default:
+                    Debug.Fail($"Unexpected class type: {converter.ConverterStrategy}");
+                    throw new InvalidOperationException();
+            }
         }
 
         private sealed class JsonPropertyInfoList : ConfigurationList<JsonPropertyInfo>
@@ -1182,17 +1190,11 @@ namespace System.Text.Json.Serialization.Metadata
             private readonly JsonTypeInfo _jsonTypeInfo;
 
             public JsonPropertyInfoList(JsonTypeInfo jsonTypeInfo)
-                : base(jsonTypeInfo.PropertyCache?.Values)
             {
-                if (jsonTypeInfo.ExtensionDataProperty is not null)
-                {
-                    _list.Add(jsonTypeInfo.ExtensionDataProperty);
-                }
-
                 _jsonTypeInfo = jsonTypeInfo;
             }
 
-            protected override bool IsImmutable => _jsonTypeInfo.IsConfigured || _jsonTypeInfo.Kind != JsonTypeInfoKind.Object;
+            protected override bool IsImmutable => _jsonTypeInfo.IsReadOnly || _jsonTypeInfo.Kind != JsonTypeInfoKind.Object;
             protected override void VerifyMutable()
             {
                 _jsonTypeInfo.VerifyMutable();
@@ -1207,6 +1209,9 @@ namespace System.Text.Json.Serialization.Metadata
             {
                 item.EnsureChildOf(_jsonTypeInfo);
             }
+
+            public void SortProperties()
+                => _list.StableSortByKey(static propInfo => propInfo.Order);
         }
 
         [DebuggerBrowsable(DebuggerBrowsableState.Never)]
index df47486..6c2a0a5 100644 (file)
@@ -10,12 +10,18 @@ namespace System.Text.Json.Serialization.Metadata
     /// Provides JSON serialization-related metadata about a type.
     /// </summary>
     /// <typeparam name="T">The generic definition of the type.</typeparam>
-    public abstract partial class JsonTypeInfo<T> : JsonTypeInfo
+    public sealed partial class JsonTypeInfo<T> : JsonTypeInfo
     {
         private Action<Utf8JsonWriter, T>? _serialize;
 
         private Func<T>? _typedCreateObject;
 
+        internal JsonTypeInfo(JsonConverter converter, JsonSerializerOptions options)
+            : base(typeof(T), converter, options)
+        {
+            EffectiveConverter = converter.CreateCastingConverter<T>();
+        }
+
         /// <summary>
         /// A Converter whose declared type always matches that of the current JsonTypeInfo.
         /// It might be the same instance as JsonTypeInfo.Converter or it could be wrapped
@@ -93,24 +99,6 @@ namespace System.Text.Json.Serialization.Metadata
             _typedCreateObject = typedCreateObject;
         }
 
-        private protected void SetCreateObjectIfCompatible(Delegate? createObject)
-        {
-            Debug.Assert(!IsConfigured);
-
-            // Guard against the reflection resolver/source generator attempting to pass
-            // a CreateObject delegate to converters/metadata that do not support it.
-            if (Converter.SupportsCreateObjectDelegate && !Converter.ConstructorIsParameterized)
-            {
-                SetCreateObject(createObject);
-            }
-        }
-
-        internal JsonTypeInfo(JsonConverter converter, JsonSerializerOptions options)
-            : base(typeof(T), converter, options)
-        {
-            EffectiveConverter = converter.CreateCastingConverter<T>();
-        }
-
         /// <summary>
         /// Serializes an instance of <typeparamref name="T"/> using
         /// <see cref="JsonSourceGenerationOptionsAttribute"/> values specified at design time.
@@ -123,9 +111,9 @@ namespace System.Text.Json.Serialization.Metadata
             {
                 return _serialize;
             }
-            private protected set
+            internal set
             {
-                Debug.Assert(!IsConfigured, "We should not mutate configured JsonTypeInfo");
+                Debug.Assert(!IsReadOnly, "We should not mutate read-only JsonTypeInfo");
                 _serialize = value;
                 CanUseSerializeHandler = value != null;
             }
@@ -135,7 +123,7 @@ namespace System.Text.Json.Serialization.Metadata
         {
             return new JsonPropertyInfo<T>(
                 declaringType: typeof(T),
-                declaringTypeInfo: null,
+                declaringTypeInfo: this,
                 Options)
             {
                 JsonTypeInfo = this,
@@ -150,43 +138,5 @@ namespace System.Text.Json.Serialization.Metadata
                 JsonTypeInfo = this
             };
         }
-
-        private protected void PopulatePolymorphismMetadata()
-        {
-            JsonPolymorphismOptions? options = JsonPolymorphismOptions.CreateFromAttributeDeclarations(Type);
-            if (options != null)
-            {
-                options.DeclaringTypeInfo = this;
-                _polymorphismOptions = options;
-            }
-        }
-
-        private protected void MapInterfaceTypesToCallbacks()
-        {
-            // Callbacks currently only supported in object kinds
-            // TODO: extend to collections/dictionaries
-            if (Kind == JsonTypeInfoKind.Object)
-            {
-                if (typeof(IJsonOnSerializing).IsAssignableFrom(typeof(T)))
-                {
-                    OnSerializing = static obj => ((IJsonOnSerializing)obj).OnSerializing();
-                }
-
-                if (typeof(IJsonOnSerialized).IsAssignableFrom(typeof(T)))
-                {
-                    OnSerialized = static obj => ((IJsonOnSerialized)obj).OnSerialized();
-                }
-
-                if (typeof(IJsonOnDeserializing).IsAssignableFrom(typeof(T)))
-                {
-                    OnDeserializing = static obj => ((IJsonOnDeserializing)obj).OnDeserializing();
-                }
-
-                if (typeof(IJsonOnDeserialized).IsAssignableFrom(typeof(T)))
-                {
-                    OnDeserialized = static obj => ((IJsonOnDeserialized)obj).OnDeserialized();
-                }
-            }
-        }
     }
 }
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/SourceGenJsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/SourceGenJsonTypeInfoOfT.cs
deleted file mode 100644 (file)
index 8c1414d..0000000
+++ /dev/null
@@ -1,176 +0,0 @@
-// 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.Diagnostics;
-using System.Reflection;
-using System.Text.Json.Serialization.Converters;
-
-namespace System.Text.Json.Serialization.Metadata
-{
-    /// <summary>
-    /// Creates and initializes serialization metadata for a type.
-    /// </summary>
-    /// <typeparam name="T"></typeparam>
-    internal sealed class SourceGenJsonTypeInfo<T> : JsonTypeInfo<T>
-    {
-        /// <summary>
-        /// Creates serialization metadata for a type using a simple converter.
-        /// </summary>
-        public SourceGenJsonTypeInfo(JsonConverter converter, JsonSerializerOptions options)
-            : base(converter, options)
-        {
-            PopulatePolymorphismMetadata();
-            MapInterfaceTypesToCallbacks();
-
-            // Plug in any converter configuration -- should be run last.
-            converter.ConfigureJsonTypeInfo(this, options);
-        }
-
-        /// <summary>
-        /// Creates serialization metadata for an object.
-        /// </summary>
-        public SourceGenJsonTypeInfo(JsonSerializerOptions options, JsonObjectInfoValues<T> objectInfo) : base(GetConverter(objectInfo), options)
-        {
-            if (objectInfo.ObjectWithParameterizedConstructorCreator != null)
-            {
-                CreateObjectWithArgs = objectInfo.ObjectWithParameterizedConstructorCreator;
-                CtorParamInitFunc = objectInfo.ConstructorParameterMetadataInitializer;
-            }
-            else
-            {
-                SetCreateObjectIfCompatible(objectInfo.ObjectCreator);
-                CreateObjectForExtensionDataProperty = ((JsonTypeInfo)this).CreateObject;
-            }
-
-            PropInitFunc = objectInfo.PropertyMetadataInitializer;
-            SerializeHandler = objectInfo.SerializeHandler;
-            NumberHandling = objectInfo.NumberHandling;
-            PopulatePolymorphismMetadata();
-            MapInterfaceTypesToCallbacks();
-
-            // Plug in any converter configuration -- should be run last.
-            Converter.ConfigureJsonTypeInfo(this, options);
-        }
-
-        /// <summary>
-        /// Creates serialization metadata for a collection.
-        /// </summary>
-        public SourceGenJsonTypeInfo(
-            JsonSerializerOptions options,
-            JsonCollectionInfoValues<T> collectionInfo,
-            Func<JsonConverter<T>> converterCreator,
-            object? createObjectWithArgs = null,
-            object? addFunc = null)
-            : base(new JsonMetadataServicesConverter<T>(converterCreator()), options)
-        {
-            if (collectionInfo is null)
-            {
-                ThrowHelper.ThrowArgumentNullException(nameof(collectionInfo));
-            }
-
-            KeyTypeInfo = collectionInfo.KeyInfo;
-            ElementTypeInfo = collectionInfo.ElementInfo;
-            Debug.Assert(Kind != JsonTypeInfoKind.None);
-            NumberHandling = collectionInfo.NumberHandling;
-            SerializeHandler = collectionInfo.SerializeHandler;
-            CreateObjectWithArgs = createObjectWithArgs;
-            AddMethodDelegate = addFunc;
-            SetCreateObjectIfCompatible(collectionInfo.ObjectCreator);
-            PopulatePolymorphismMetadata();
-            MapInterfaceTypesToCallbacks();
-
-            // Plug in any converter configuration -- should be run last.
-            Converter.ConfigureJsonTypeInfo(this, options);
-        }
-
-        private static JsonMetadataServicesConverter<T> GetConverter(JsonObjectInfoValues<T> objectInfo)
-        {
-#pragma warning disable CS8714
-            // The type cannot be used as type parameter in the generic type or method.
-            // Nullability of type argument doesn't match 'notnull' constraint.
-            if (objectInfo.ObjectWithParameterizedConstructorCreator != null)
-            {
-                return new JsonMetadataServicesConverter<T>(
-                    () => new LargeObjectWithParameterizedConstructorConverter<T>(),
-                    ConverterStrategy.Object);
-            }
-            else
-            {
-                return new JsonMetadataServicesConverter<T>(() => new ObjectDefaultConverter<T>(), ConverterStrategy.Object);
-            }
-#pragma warning restore CS8714
-        }
-
-        internal override JsonParameterInfoValues[] GetParameterInfoValues()
-        {
-            JsonParameterInfoValues[] array;
-            if (CtorParamInitFunc == null || (array = CtorParamInitFunc()) == null)
-            {
-                array = Array.Empty<JsonParameterInfoValues>();
-                MetadataSerializationNotSupported = true;
-            }
-
-            return array;
-        }
-
-        internal override void LateAddProperties()
-        {
-            Debug.Assert(!IsConfigured);
-            Debug.Assert(PropertyCache is null);
-
-            if (Kind != JsonTypeInfoKind.Object)
-            {
-                return;
-            }
-
-            JsonSerializerContext? context = Options.TypeInfoResolver as JsonSerializerContext;
-            JsonPropertyInfo[] array;
-            if (PropInitFunc == null || (array = PropInitFunc(context!)) == null)
-            {
-                if (typeof(T) == typeof(object))
-                {
-                    return;
-                }
-
-                if (Converter.ElementType != null)
-                {
-                    // Nullable<> or F# optional converter's strategy is set to element's strategy
-                    return;
-                }
-
-                MetadataSerializationNotSupported = true;
-                return;
-            }
-
-            Dictionary<string, JsonPropertyInfo>? ignoredMembers = null;
-            JsonPropertyDictionary<JsonPropertyInfo> propertyCache = CreatePropertyCache(capacity: array.Length);
-
-            for (int i = 0; i < array.Length; i++)
-            {
-                JsonPropertyInfo jsonPropertyInfo = array[i];
-                bool hasJsonInclude = jsonPropertyInfo.SrcGen_HasJsonInclude;
-
-                if (!jsonPropertyInfo.SrcGen_IsPublic)
-                {
-                    if (hasJsonInclude)
-                    {
-                        Debug.Assert(jsonPropertyInfo.MemberName != null, "MemberName is not set by source gen");
-                        ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(jsonPropertyInfo.MemberName, jsonPropertyInfo.DeclaringType);
-                    }
-
-                    continue;
-                }
-
-                if (jsonPropertyInfo.MemberType == MemberTypes.Field && !hasJsonInclude && !Options.IncludeFields)
-                {
-                    continue;
-                }
-
-                CacheMember(jsonPropertyInfo, propertyCache, ref ignoredMembers);
-            }
-
-            PropertyCache = propertyCache;
-        }
-    }
-}
index 419732a..f99de65 100644 (file)
@@ -8,7 +8,6 @@ using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.Reflection;
-using System.Runtime.CompilerServices;
 using System.Text.Json.Serialization;
 using System.Text.Json.Serialization.Metadata;
 
@@ -226,9 +225,9 @@ namespace System.Text.Json
             // Soft cut-off length - once message becomes longer than that we won't be adding more elements
             const int CutOffLength = 50;
 
-            for (int propertyIdx = 0; propertyIdx < parent.PropertyCache.List.Count; propertyIdx++)
+            foreach (KeyValuePair<string, JsonPropertyInfo> kvp in parent.PropertyCache.List)
             {
-                JsonPropertyInfo property = parent.PropertyCache.List[propertyIdx].Value;
+                JsonPropertyInfo property = kvp.Value;
 
                 if (!property.IsRequired || requiredPropertiesSet[property.RequiredPropertyIndex])
                 {