Add JSON source-gen mode that emits serialization logic (#53212)
authorLayomi Akinrinade <laakinri@microsoft.com>
Thu, 27 May 2021 06:38:01 +0000 (02:38 -0400)
committerGitHub <noreply@github.com>
Thu, 27 May 2021 06:38:01 +0000 (02:38 -0400)
* Add JSON source-gen mode that emits serialization logic

* Fix System.Net.Http.Json test issues

* Fix System.Text.Json test issues

* Make check to determine if fast-path can be used more efficient

* Address review feedback

* Improve derived-JsonSerializerContext detection and support

* Address review feedback; reenable tests, and simplify object metadata
init

* Fix formatting

77 files changed:
src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/JsonContext.cs
src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs
src/libraries/System.Text.Json/Common/JsonAttribute.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonAttribute.cs with 70% similarity]
src/libraries/System.Text.Json/Common/JsonCamelCaseNamingPolicy.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonCamelCaseNamingPolicy.cs with 100% similarity]
src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs [new file with mode: 0644]
src/libraries/System.Text.Json/Common/JsonNamingPolicy.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonNamingPolicy.cs with 88% similarity]
src/libraries/System.Text.Json/Common/JsonSerializerOptionsAttribute.cs [new file with mode: 0644]
src/libraries/System.Text.Json/Common/JsonSourceGenerationMode.cs [new file with mode: 0644]
src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs [new file with mode: 0644]
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs
src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs [moved from src/libraries/System.Text.Json/gen/PropertyMetadata.cs with 80% similarity]
src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs
src/libraries/System.Text.Json/gen/Resources/Strings.resx
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf
src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf
src/libraries/System.Text.Json/gen/SourceGenerationSpec.cs [new file with mode: 0644]
src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj
src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs [new file with mode: 0644]
src/libraries/System.Text.Json/gen/TypeMetadata.cs [deleted file]
src/libraries/System.Text.Json/ref/System.Text.Json.cs
src/libraries/System.Text.Json/src/Resources/Strings.resx
src/libraries/System.Text.Json/src/System.Text.Json.csproj
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonSerializableAttribute.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectSourceGenConverter.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonDefaultNamingPolicy.cs [deleted file]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonResumableConverterOfT.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/JsonSerializerContext.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.Converters.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.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/JsonTypeInfoInternalOfT.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonTestHelper.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs [moved from src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGeneratorTests.cs with 74% similarity]
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationLogicTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/CompilationHelper.cs
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorDiagnosticsTests.cs
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/JsonSourceGeneratorTests.cs
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.UnitTests/TypeWrapperTests.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonTestHelper.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/Dictionary.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/HighLowTemps.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/JsonContext.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/List.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/StringArray.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/JsonContext/WeatherForecastWithPOCOs.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.JsonMetadataServices.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/MetadataTests.Options.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj

index 452644e..56a4ff5 100644 (file)
@@ -13,11 +13,11 @@ namespace System.Net.Http.Json.Functional.Tests
         private static JsonContext s_default;
         public static JsonContext Default => s_default ??= new JsonContext(new JsonSerializerOptions());
 
-        public JsonContext() : base(null)
+        public JsonContext() : base(null, null)
         {
         }
 
-        public JsonContext(JsonSerializerOptions options) : base(options)
+        public JsonContext(JsonSerializerOptions options) : base(options, null)
         {
         }
 
index f383d17..36785e9 100644 (file)
@@ -23,15 +23,14 @@ namespace System.Net.Http.Json.Functional.Tests
                     }
                     else
                     {
-                        JsonTypeInfo<Person> objectInfo = JsonMetadataServices.CreateObjectInfo<Person>();
-                        _Person = objectInfo;
-
-                        JsonMetadataServices.InitializeObjectInfo(
-                            objectInfo,
+                        JsonTypeInfo<Person> objectInfo = JsonMetadataServices.CreateObjectInfo<Person>(
                             Options,
                             createObjectFunc: static () => new Person(),
                             PersonPropInitFunc,
-                            default);
+                            default,
+                            serializeFunc: null);
+
+                        _Person = objectInfo;
                     }
                 }
 
@@ -6,5 +6,10 @@ namespace System.Text.Json.Serialization
     /// <summary>
     /// The base class of serialization attributes.
     /// </summary>
-    public abstract class JsonAttribute : Attribute { }
+#if BUILDING_SOURCE_GENERATOR
+    internal
+#else
+    public
+#endif
+    abstract class JsonAttribute : Attribute { }
 }
diff --git a/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs
new file mode 100644 (file)
index 0000000..9929071
--- /dev/null
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Text.Json.Serialization
+{
+    /// <summary>
+    /// The <see cref="Json.JsonNamingPolicy"/> to be used at run-time.
+    /// </summary>
+#if BUILDING_SOURCE_GENERATOR
+    internal
+#else
+    public
+#endif
+    enum JsonKnownNamingPolicy
+    {
+        /// <summary>
+        /// Specifies that JSON property names should not be converted.
+        /// </summary>
+        Unspecified = 0,
+
+        /// <summary>
+        /// Specifies that the built-in <see cref="Json.JsonNamingPolicy.CamelCase"/> be used to convert JSON property names.
+        /// </summary>
+        BuiltInCamelCase = 1
+    }
+}
@@ -6,7 +6,12 @@ namespace System.Text.Json
     /// <summary>
     /// Determines the naming policy used to convert a string-based name to another format, such as a camel-casing format.
     /// </summary>
-    public abstract class JsonNamingPolicy
+#if BUILDING_SOURCE_GENERATOR
+    internal
+#else
+    public
+#endif
+    abstract class JsonNamingPolicy
     {
         /// <summary>
         /// Initializes a new instance of <see cref="JsonNamingPolicy"/>.
@@ -18,8 +23,6 @@ namespace System.Text.Json
         /// </summary>
         public static JsonNamingPolicy CamelCase { get; } = new JsonCamelCaseNamingPolicy();
 
-        internal static JsonNamingPolicy Default { get; } = new JsonDefaultNamingPolicy();
-
         /// <summary>
         /// When overridden in a derived class, converts the specified name according to the policy.
         /// </summary>
diff --git a/src/libraries/System.Text.Json/Common/JsonSerializerOptionsAttribute.cs b/src/libraries/System.Text.Json/Common/JsonSerializerOptionsAttribute.cs
new file mode 100644 (file)
index 0000000..a1b4d09
--- /dev/null
@@ -0,0 +1,53 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Text.Json.Serialization
+{
+    /// <summary>
+    /// Instructs the System.Text.Json source generator to assume the specified
+    /// options will be used at run-time via <see cref="JsonSerializerOptions"/>.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
+#if BUILDING_SOURCE_GENERATOR
+    internal
+#else
+    public
+#endif
+    class JsonSerializerOptionsAttribute : JsonAttribute
+    {
+        /// <summary>
+        /// Specifies the default ignore condition.
+        /// </summary>
+        public JsonIgnoreCondition DefaultIgnoreCondition { get; set; }
+
+        /// <summary>
+        /// Specifies whether to ignore read-only fields.
+        /// </summary>
+        public bool IgnoreReadOnlyFields { get; set; }
+
+        /// <summary>
+        /// Specifies whether to ignore read-only properties.
+        /// </summary>
+        public bool IgnoreReadOnlyProperties { get; set; }
+
+        /// <summary>
+        /// Specifies whether to ignore custom converters provided at run-time.
+        /// </summary>
+        public bool IgnoreRuntimeCustomConverters { get; set; }
+
+        /// <summary>
+        /// Specifies whether to include fields for serialization and deserialization.
+        /// </summary>
+        public bool IncludeFields { get; set; }
+
+        /// <summary>
+        /// Specifies a built-in naming polices to convert JSON property names with.
+        /// </summary>
+        public JsonKnownNamingPolicy NamingPolicy { get; set; }
+
+        /// <summary>
+        /// Specifies whether JSON output should be pretty-printed.
+        /// </summary>
+        public bool WriteIndented { get; set; }
+    }
+}
diff --git a/src/libraries/System.Text.Json/Common/JsonSourceGenerationMode.cs b/src/libraries/System.Text.Json/Common/JsonSourceGenerationMode.cs
new file mode 100644 (file)
index 0000000..b46292b
--- /dev/null
@@ -0,0 +1,42 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Text.Json.Serialization
+{
+    /// <summary>
+    /// The generation mode for the System.Text.Json source generator.
+    /// </summary>
+    [Flags]
+#if BUILDING_SOURCE_GENERATOR
+    internal
+#else
+    public
+#endif
+    enum JsonSourceGenerationMode
+    {
+        /// <summary>
+        /// Instructs the JSON source generator to generate serialization logic and type metadata to fallback to
+        /// when the run-time options are not compatible with the indicated <see cref="JsonSerializerOptionsAttribute"/>.
+        /// </summary>
+        /// <remarks>
+        /// This mode supports all <see cref="JsonSerializer"/> features.
+        /// </remarks>
+        MetadataAndSerialization = 0,
+
+        /// <summary>
+        /// Instructs the JSON source generator to generate type-metadata initialization logic.
+        /// </summary>
+        /// <remarks>
+        /// This mode supports all <see cref="JsonSerializer"/> features.
+        /// </remarks>
+        Metadata = 1,
+
+        /// <summary>
+        /// Instructs the JSON source generator to generate serialization logic.
+        /// </summary>
+        /// <remarks>
+        /// This mode supports only a subset of <see cref="JsonSerializer"/> features.
+        /// </remarks>
+        Serialization = 2
+    }
+}
diff --git a/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs b/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs
new file mode 100644 (file)
index 0000000..7ba239f
--- /dev/null
@@ -0,0 +1,37 @@
+// 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;
+using System.Text.Json.SourceGeneration.Reflection;
+
+namespace System.Text.Json.SourceGeneration
+{
+    /// <summary>
+    /// Represents the set of input types and options needed to provide an
+    /// implementation for a user-provided JsonSerializerContext-derived type.
+    /// </summary>
+    internal sealed class ContextGenerationSpec
+    {
+        public JsonSerializerOptionsAttribute SerializerOptions { get; init; }
+
+        public Type ContextType { get; init; }
+
+        public List<TypeGenerationSpec>? RootSerializableTypes { get; init; }
+
+        public List<string> ContextClassDeclarationList { get; init; }
+
+        /// <summary>
+        /// Types that we have initiated serialization metadata generation for. A type may be discoverable in the object graph,
+        /// but not reachable for serialization (e.g. it is [JsonIgnore]'d); thus we maintain a separate cache.
+        /// </summary>
+        public HashSet<TypeGenerationSpec> TypesWithMetadataGenerated { get; } = new();
+
+        /// <summary>
+        /// Cache of runtime property names (statically determined) found accross the object graph of the JsonSerializerContext.
+        /// </summary>
+        public HashSet<string> RuntimePropertyNames { get; } = new();
+
+        public string ContextTypeRef => $"global::{ContextType.GetUniqueCompilableTypeName()}";
+    }
+}
index a176de6..251aa2a 100644 (file)
@@ -3,9 +3,8 @@
 
 using System.Collections.Generic;
 using System.Diagnostics;
-using System.Linq;
+using System.Reflection;
 using System.Text.Json.Serialization;
-using System.Text.Json.SourceGeneration.Reflection;
 using Microsoft.CodeAnalysis;
 using Microsoft.CodeAnalysis.Text;
 
@@ -13,21 +12,48 @@ namespace System.Text.Json.SourceGeneration
 {
     public sealed partial class JsonSourceGenerator
     {
-        private sealed class Emitter
+        private sealed partial class Emitter
         {
+            // Literals in generated source
             private const string RuntimeCustomConverterFetchingMethodName = "GetRuntimeProvidedCustomConverter";
-
-            private const string JsonContextDeclarationSource = "internal partial class JsonContext : JsonSerializerContext";
-
             private const string OptionsInstanceVariableName = "Options";
-
-            private const string PropInitFuncVarName = "PropInitFunc";
-
-            private const string JsonMetadataServicesClassName = "JsonMetadataServices";
-
+            private const string PropInitMethodNameSuffix = "PropInit";
+            private const string SerializeMethodNameSuffix = "Serialize";
             private const string CreateValueInfoMethodName = "CreateValueInfo";
+            private const string DefaultOptionsStaticVarName = "s_defaultOptions";
+            private const string DefaultContextBackingStaticVarName = "s_defaultContext";
+            private const string WriterVarName = "writer";
+            private const string ValueVarName = "value";
+            private const string JsonSerializerContextName = "JsonSerializerContext";
+
+            private static AssemblyName _assemblyName = typeof(Emitter).Assembly.GetName();
+            private static readonly string s_generatedCodeAttributeSource = $@"
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{_assemblyName.Name}"", ""{_assemblyName.Version}"")]
+";
 
-            private const string SystemTextJsonSourceGenerationName = "System.Text.Json.SourceGeneration";
+            // global::fully.qualified.name for referenced types
+            private const string ArrayTypeRef = "global::System.Array";
+            private const string InvalidOperationExceptionTypeRef = "global::System.InvalidOperationException";
+            private const string TypeTypeRef = "global::System.Type";
+            private const string UnsafeTypeRef = "global::System.CompilerServices.Unsafe";
+            private const string NullableTypeRef = "global::System.Nullable";
+            private const string IListTypeRef = "global::System.Collections.Generic.IList";
+            private const string KeyValuePairTypeRef = "global::System.Collections.Generic.KeyValuePair";
+            private const string ListTypeRef = "global::System.Collections.Generic.List";
+            private const string DictionaryTypeRef = "global::System.Collections.Generic.Dictionary";
+            private const string JsonEncodedTextTypeRef = "global::System.Text.Json.JsonEncodedText";
+            private const string JsonNamingPolicyTypeRef = "global::System.Text.Json.JsonNamingPolicy";
+            private const string JsonSerializerTypeRef = "global::System.Text.Json.JsonSerializer";
+            private const string JsonSerializerOptionsTypeRef = "global::System.Text.Json.JsonSerializerOptions";
+            private const string Utf8JsonWriterTypeRef = "global::System.Text.Json.Utf8JsonWriter";
+            private const string JsonConverterTypeRef = "global::System.Text.Json.Serialization.JsonConverter";
+            private const string JsonConverterFactoryTypeRef = "global::System.Text.Json.Serialization.JsonConverterFactory";
+            private const string JsonIgnoreConditionTypeRef = "global::System.Text.Json.Serialization.JsonIgnoreCondition";
+            private const string JsonNumberHandlingTypeRef = "global::System.Text.Json.Serialization.JsonNumberHandling";
+            private const string JsonSerializerContextTypeRef = "global::System.Text.Json.Serialization.JsonSerializerContext";
+            private const string JsonMetadataServicesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonMetadataServices";
+            private const string JsonPropertyInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo";
+            private const string JsonTypeInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonTypeInfo";
 
             private static DiagnosticDescriptor TypeNotSupported { get; } = new DiagnosticDescriptor(
                 id: "SYSLIB1030",
@@ -45,109 +71,150 @@ namespace System.Text.Json.SourceGeneration
                 defaultSeverity: DiagnosticSeverity.Warning,
                 isEnabledByDefault: true);
 
-            private readonly string _generationNamespace;
-
-            // TODO (https://github.com/dotnet/runtime/issues/52218): consider public option for this.
-            // Converter-honoring logic generation can be simplified
-            // if we don't plan to have a feature around this.
-            private readonly bool _honorRuntimeProvidedCustomConverters = true;
-
             private readonly GeneratorExecutionContext _executionContext;
 
-            /// <summary>
-            /// Types that we have initiated serialization metadata generation for. A type may be discoverable in the object graph,
-            /// but not reachable for serialization (e.g. it is [JsonIgnore]'d); thus we maintain a separate cache.
-            /// </summary>
-            private readonly HashSet<TypeMetadata> _typesWithMetadataGenerated = new();
+            private ContextGenerationSpec _currentContext = null!;
 
-            /// <summary>
-            /// Types that were specified with System.Text.Json.Serialization.JsonSerializableAttribute.
-            /// </summary>
-            private readonly Dictionary<string, TypeMetadata> _rootSerializableTypes = null!;
+            private readonly SourceGenerationSpec _generationSpec = null!;
 
-            public Emitter(in GeneratorExecutionContext executionContext, Dictionary<string, TypeMetadata> rootSerializableTypes)
+            public Emitter(in GeneratorExecutionContext executionContext, SourceGenerationSpec generationSpec)
             {
                 _executionContext = executionContext;
-                _generationNamespace = $"{executionContext.Compilation.AssemblyName}.JsonSourceGeneration";
-                _rootSerializableTypes = rootSerializableTypes;
+                _generationSpec = generationSpec;
             }
 
             public void Emit()
             {
-                foreach (KeyValuePair<string, TypeMetadata> pair in _rootSerializableTypes)
+                foreach (ContextGenerationSpec contextGenerationSpec in _generationSpec.ContextGenerationSpecList)
                 {
-                    TypeMetadata typeMetadata = pair.Value;
-                    GenerateTypeMetadata(typeMetadata);
+                    _currentContext = contextGenerationSpec;
+
+                    foreach (TypeGenerationSpec typeGenerationSpec in _currentContext.RootSerializableTypes)
+                    {
+                        GenerateTypeInfo(typeGenerationSpec);
+                    }
+
+                    string contextName = _currentContext.ContextType.Name;
+
+                    // Add root context implementation.
+                    AddSource($"{contextName}.g.cs", GetRootJsonContextImplementation(), isRootContextDef: true);
+
+                    // Add GetJsonTypeInfo override implementation.
+                    AddSource($"{contextName}.GetJsonTypeInfo.g.cs", GetGetTypeInfoImplementation());
+
+                    // Add property name initialization.
+                    AddSource($"{contextName}.PropertyNames.g.cs", GetPropertyNameInitialization());
                 }
+            }
+
+            private void AddSource(string fileName, string source, bool isRootContextDef = false)
+            {
+                string? generatedCodeAttributeSource = isRootContextDef ? s_generatedCodeAttributeSource : null;
+
+                List<string> declarationList = _currentContext.ContextClassDeclarationList;
+                int declarationCount = declarationList.Count;
+                Debug.Assert(declarationCount >= 1);
+
+                StringBuilder sb = new();
 
-                // Add base default instance source.
-                _executionContext.AddSource("JsonContext.g.cs", SourceText.From(GetBaseJsonContextImplementation(), Encoding.UTF8));
+                sb.Append($@"// <auto-generated/>
 
-                // Add GetJsonTypeInfo override implementation.
-                _executionContext.AddSource("JsonContext.GetJsonTypeInfo.g.cs", SourceText.From(GetGetTypeInfoImplementation(), Encoding.UTF8));
+namespace {_currentContext.ContextType.Namespace}
+{{");
+
+                for (int i = 0; i < declarationCount - 1; i++)
+                {
+                    string declarationSource = $@"
+{declarationList[declarationCount - 1 - i]}
+{{";
+                    sb.Append($@"
+{IndentSource(declarationSource, numIndentations: i + 1)}
+");
+                }
+
+                // Add the core implementation for the derived context class.
+                string partialContextImplementation = $@"
+{generatedCodeAttributeSource}{declarationList[0]}
+{{
+    {IndentSource(source, Math.Max(1, declarationCount - 1))}
+}}";
+                sb.AppendLine(IndentSource(partialContextImplementation, numIndentations: declarationCount));
+
+                // Match curly brace for each containing type.
+                for (int i = 0; i < declarationCount - 1; i++)
+                {
+                    sb.AppendLine(IndentSource("}", numIndentations: declarationCount + i + 1));
+                }
+
+                // Match curly brace for namespace.
+                sb.AppendLine("}");
+
+                _executionContext.AddSource(fileName, SourceText.From(sb.ToString(), Encoding.UTF8));
             }
 
-            private void GenerateTypeMetadata(TypeMetadata typeMetadata)
+            private void GenerateTypeInfo(TypeGenerationSpec typeGenerationSpec)
             {
-                Debug.Assert(typeMetadata != null);
+                Debug.Assert(typeGenerationSpec != null);
+
+                HashSet<TypeGenerationSpec> typesWithMetadata = _currentContext.TypesWithMetadataGenerated;
 
-                if (_typesWithMetadataGenerated.Contains(typeMetadata))
+                if (typesWithMetadata.Contains(typeGenerationSpec))
                 {
                     return;
                 }
 
-                _typesWithMetadataGenerated.Add(typeMetadata);
+                typesWithMetadata.Add(typeGenerationSpec);
 
                 string source;
 
-                switch (typeMetadata.ClassType)
+                switch (typeGenerationSpec.ClassType)
                 {
                     case ClassType.KnownType:
                         {
-                            source = GenerateForTypeWithKnownConverter(typeMetadata);
+                            source = GenerateForTypeWithKnownConverter(typeGenerationSpec);
                         }
                         break;
                     case ClassType.TypeWithDesignTimeProvidedCustomConverter:
                         {
-                            source = GenerateForTypeWithUnknownConverter(typeMetadata);
+                            source = GenerateForTypeWithUnknownConverter(typeGenerationSpec);
                         }
                         break;
                     case ClassType.Nullable:
                         {
-                            source = GenerateForNullable(typeMetadata);
+                            source = GenerateForNullable(typeGenerationSpec);
 
-                            GenerateTypeMetadata(typeMetadata.NullableUnderlyingTypeMetadata);
+                            GenerateTypeInfo(typeGenerationSpec.NullableUnderlyingTypeMetadata);
                         }
                         break;
                     case ClassType.Enum:
                         {
-                            source = GenerateForEnum(typeMetadata);
+                            source = GenerateForEnum(typeGenerationSpec);
                         }
                         break;
                     case ClassType.Enumerable:
                         {
-                            source = GenerateForCollection(typeMetadata);
+                            source = GenerateForCollection(typeGenerationSpec);
 
-                            GenerateTypeMetadata(typeMetadata.CollectionValueTypeMetadata);
+                            GenerateTypeInfo(typeGenerationSpec.CollectionValueTypeMetadata);
                         }
                         break;
                     case ClassType.Dictionary:
                         {
-                            source = GenerateForCollection(typeMetadata);
+                            source = GenerateForCollection(typeGenerationSpec);
 
-                            GenerateTypeMetadata(typeMetadata.CollectionKeyTypeMetadata);
-                            GenerateTypeMetadata(typeMetadata.CollectionValueTypeMetadata);
+                            GenerateTypeInfo(typeGenerationSpec.CollectionKeyTypeMetadata);
+                            GenerateTypeInfo(typeGenerationSpec.CollectionValueTypeMetadata);
                         }
                         break;
                     case ClassType.Object:
                         {
-                            source = GenerateForObject(typeMetadata);
+                            source = GenerateForObject(typeGenerationSpec);
 
-                            if (typeMetadata.PropertiesMetadata != null)
+                            if (typeGenerationSpec.PropertiesMetadata != null)
                             {
-                                foreach (PropertyMetadata metadata in typeMetadata.PropertiesMetadata)
+                                foreach (PropertyGenerationSpec metadata in typeGenerationSpec.PropertiesMetadata)
                                 {
-                                    GenerateTypeMetadata(metadata.TypeMetadata);
+                                    GenerateTypeInfo(metadata.TypeGenerationSpec);
                                 }
                             }
                         }
@@ -155,7 +222,7 @@ namespace System.Text.Json.SourceGeneration
                     case ClassType.TypeUnsupportedBySourceGen:
                         {
                             _executionContext.ReportDiagnostic(
-                                Diagnostic.Create(TypeNotSupported, Location.None, new string[] { typeMetadata.CompilableName }));
+                                Diagnostic.Create(TypeNotSupported, Location.None, new string[] { typeGenerationSpec.TypeRef }));
                             return;
                         }
                     default:
@@ -166,109 +233,109 @@ namespace System.Text.Json.SourceGeneration
 
                 try
                 {
-                    _executionContext.AddSource($"{typeMetadata.FriendlyName}.cs", SourceText.From(source, Encoding.UTF8));
+                    AddSource($"{_currentContext.ContextType.Name}.{typeGenerationSpec.TypeInfoPropertyName}.g.cs", source);
                 }
                 catch (ArgumentException)
                 {
-                    _executionContext.ReportDiagnostic(Diagnostic.Create(DuplicateTypeName, Location.None, new string[] { typeMetadata.FriendlyName }));
+                    _executionContext.ReportDiagnostic(Diagnostic.Create(DuplicateTypeName, Location.None, new string[] { typeGenerationSpec.TypeInfoPropertyName }));
                 }
             }
 
-            private string GenerateForTypeWithKnownConverter(TypeMetadata typeMetadata)
+            private string GenerateForTypeWithKnownConverter(TypeGenerationSpec typeMetadata)
             {
-                string typeCompilableName = typeMetadata.CompilableName;
-                string typeFriendlyName = typeMetadata.FriendlyName;
+                string typeCompilableName = typeMetadata.TypeRef;
+                string typeFriendlyName = typeMetadata.TypeInfoPropertyName;
 
-                string metadataInitSource = $@"_{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, {JsonMetadataServicesClassName}.{typeFriendlyName}Converter);";
+                string metadataInitSource = $@"_{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, {JsonMetadataServicesTypeRef}.{typeFriendlyName}Converter);";
 
                 return GenerateForType(typeMetadata, metadataInitSource);
             }
 
-            private string GenerateForTypeWithUnknownConverter(TypeMetadata typeMetadata)
+            private string GenerateForTypeWithUnknownConverter(TypeGenerationSpec typeMetadata)
             {
-                string typeCompilableName = typeMetadata.CompilableName;
-                string typeFriendlyName = typeMetadata.FriendlyName;
+                string typeCompilableName = typeMetadata.TypeRef;
+                string typeFriendlyName = typeMetadata.TypeInfoPropertyName;
 
                 StringBuilder sb = new();
 
                 // TODO (https://github.com/dotnet/runtime/issues/52218): consider moving this verification source to common helper.
-                string metadataInitSource = $@"JsonConverter converter = {typeMetadata.ConverterInstantiationLogic};
-                    Type typeToConvert = typeof({typeCompilableName});
+                string metadataInitSource = $@"{JsonConverterTypeRef} converter = {typeMetadata.ConverterInstantiationLogic};
+                    {TypeTypeRef} typeToConvert = typeof({typeCompilableName});
                     if (!converter.CanConvert(typeToConvert))
                     {{
-                        Type underlyingType = Nullable.GetUnderlyingType(typeToConvert);
+                        {TypeTypeRef} underlyingType = {NullableTypeRef}.GetUnderlyingType(typeToConvert);
                         if (underlyingType != null && converter.CanConvert(underlyingType))
                         {{
-                            JsonConverter actualConverter = converter;
+                            {JsonConverterTypeRef} actualConverter = converter;
 
-                            if (converter is JsonConverterFactory converterFactory)
+                            if (converter is {JsonConverterFactoryTypeRef} converterFactory)
                             {{
                                 actualConverter = converterFactory.CreateConverter(underlyingType, {OptionsInstanceVariableName});
 
-                                if (actualConverter == null || actualConverter is JsonConverterFactory)
+                                if (actualConverter == null || actualConverter is {JsonConverterFactoryTypeRef})
                                 {{
-                                    throw new InvalidOperationException($""JsonConverterFactory '{{converter}} cannot return a 'null' or 'JsonConverterFactory' value."");
+                                    throw new {InvalidOperationExceptionTypeRef}($""JsonConverterFactory '{{converter}} cannot return a 'null' or 'JsonConverterFactory' value."");
                                 }}
                             }}
 
                             // Allow nullable handling to forward to the underlying type's converter.
-                            converter = {JsonMetadataServicesClassName}.GetNullableConverter<{typeCompilableName}>((JsonConverter<{typeCompilableName}>)actualConverter);
+                            converter = {JsonMetadataServicesTypeRef}.GetNullableConverter<{typeCompilableName}>(({JsonConverterTypeRef}<{typeCompilableName}>)actualConverter);
                         }}
                         else
                         {{
-                            throw new InvalidOperationException($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'."");
+                            throw new {InvalidOperationExceptionTypeRef}($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'."");
                         }}
                     }}
 
-                    _{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, converter);";
+                    _{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, converter);";
 
                 return GenerateForType(typeMetadata, metadataInitSource);
             }
 
-            private string GenerateForNullable(TypeMetadata typeMetadata)
+            private string GenerateForNullable(TypeGenerationSpec typeMetadata)
             {
-                string typeCompilableName = typeMetadata.CompilableName;
-                string typeFriendlyName = typeMetadata.FriendlyName;
+                string typeCompilableName = typeMetadata.TypeRef;
+                string typeFriendlyName = typeMetadata.TypeInfoPropertyName;
 
-                TypeMetadata? underlyingTypeMetadata = typeMetadata.NullableUnderlyingTypeMetadata;
+                TypeGenerationSpec? underlyingTypeMetadata = typeMetadata.NullableUnderlyingTypeMetadata;
                 Debug.Assert(underlyingTypeMetadata != null);
-                string underlyingTypeCompilableName = underlyingTypeMetadata.CompilableName;
-                string underlyingTypeFriendlyName = underlyingTypeMetadata.FriendlyName;
+                string underlyingTypeCompilableName = underlyingTypeMetadata.TypeRef;
+                string underlyingTypeFriendlyName = underlyingTypeMetadata.TypeInfoPropertyName;
                 string underlyingTypeInfoNamedArg = underlyingTypeMetadata.ClassType == ClassType.TypeUnsupportedBySourceGen
                     ? "underlyingTypeInfo: null"
                     : $"underlyingTypeInfo: {underlyingTypeFriendlyName}";
 
-                string metadataInitSource = @$"_{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}(
+                string metadataInitSource = @$"_{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}(
                         {OptionsInstanceVariableName},
-                        {JsonMetadataServicesClassName}.GetNullableConverter<{underlyingTypeCompilableName}>({underlyingTypeInfoNamedArg}));
+                        {JsonMetadataServicesTypeRef}.GetNullableConverter<{underlyingTypeCompilableName}>({underlyingTypeInfoNamedArg}));
 ";
 
                 return GenerateForType(typeMetadata, metadataInitSource);
             }
 
-            private string GenerateForEnum(TypeMetadata typeMetadata)
+            private string GenerateForEnum(TypeGenerationSpec typeMetadata)
             {
-                string typeCompilableName = typeMetadata.CompilableName;
-                string typeFriendlyName = typeMetadata.FriendlyName;
+                string typeCompilableName = typeMetadata.TypeRef;
+                string typeFriendlyName = typeMetadata.TypeInfoPropertyName;
 
-                string metadataInitSource = $"_{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, JsonMetadataServices.GetEnumConverter<{typeCompilableName}>({OptionsInstanceVariableName}));";
+                string metadataInitSource = $"_{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, {JsonMetadataServicesTypeRef}.GetEnumConverter<{typeCompilableName}>({OptionsInstanceVariableName}));";
 
                 return GenerateForType(typeMetadata, metadataInitSource);
             }
 
-            private string GenerateForCollection(TypeMetadata typeMetadata)
+            private string GenerateForCollection(TypeGenerationSpec typeGenerationSpec)
             {
-                string typeCompilableName = typeMetadata.CompilableName;
-                string typeFriendlyName = typeMetadata.FriendlyName;
+                string typeCompilableName = typeGenerationSpec.TypeRef;
+                string typeFriendlyName = typeGenerationSpec.TypeInfoPropertyName;
 
                 // Key metadata
-                TypeMetadata? collectionKeyTypeMetadata = typeMetadata.CollectionKeyTypeMetadata;
-                Debug.Assert(!(typeMetadata.CollectionType == CollectionType.Dictionary && collectionKeyTypeMetadata == null));
-                string? keyTypeCompilableName = collectionKeyTypeMetadata?.CompilableName;
-                string? keyTypeReadableName = collectionKeyTypeMetadata?.FriendlyName;
+                TypeGenerationSpec? collectionKeyTypeMetadata = typeGenerationSpec.CollectionKeyTypeMetadata;
+                Debug.Assert(!(typeGenerationSpec.CollectionType == CollectionType.Dictionary && collectionKeyTypeMetadata == null));
+                string? keyTypeCompilableName = collectionKeyTypeMetadata?.TypeRef;
+                string? keyTypeReadableName = collectionKeyTypeMetadata?.TypeInfoPropertyName;
 
                 string? keyTypeMetadataPropertyName;
-                if (typeMetadata.ClassType != ClassType.Dictionary)
+                if (typeGenerationSpec.ClassType != ClassType.Dictionary)
                 {
                     keyTypeMetadataPropertyName = "null";
                 }
@@ -280,102 +347,230 @@ namespace System.Text.Json.SourceGeneration
                 }
 
                 // Value metadata
-                TypeMetadata? collectionValueTypeMetadata = typeMetadata.CollectionValueTypeMetadata;
+                TypeGenerationSpec? collectionValueTypeMetadata = typeGenerationSpec.CollectionValueTypeMetadata;
                 Debug.Assert(collectionValueTypeMetadata != null);
-                string valueTypeCompilableName = collectionValueTypeMetadata.CompilableName;
-                string valueTypeReadableName = collectionValueTypeMetadata.FriendlyName;
+                string valueTypeCompilableName = collectionValueTypeMetadata.TypeRef;
+                string valueTypeReadableName = collectionValueTypeMetadata.TypeInfoPropertyName;
 
                 string valueTypeMetadataPropertyName = collectionValueTypeMetadata.ClassType == ClassType.TypeUnsupportedBySourceGen
                     ? "null"
                     : $"this.{valueTypeReadableName}";
 
-                string numberHandlingArg = $"{GetNumberHandlingAsStr(typeMetadata.NumberHandling)}";
+                string numberHandlingArg = $"{GetNumberHandlingAsStr(typeGenerationSpec.NumberHandling)}";
+
+                string serializeMethodName = $"{typeFriendlyName}{SerializeMethodNameSuffix}";
+                string serializeFuncNamedArg;
+
+                CollectionType collectionType = typeGenerationSpec.CollectionType;
+
+                string? serializeFuncSource;
+                if (!typeGenerationSpec.GenerateSerializationLogic)
+                {
+                    serializeFuncSource = null;
+                    serializeFuncNamedArg = "serializeFunc: null";
+                }
+                else
+                {
+                    bool canBeNull = typeGenerationSpec.CanBeNull;
+
+                    switch (collectionType)
+                    {
+                        case CollectionType.Array:
+                            serializeFuncSource = GenerateFastPathFuncForEnumerable(typeCompilableName, serializeMethodName, canBeNull, isArray: true, collectionValueTypeMetadata);
+                            break;
+                        case CollectionType.List:
+                            serializeFuncSource = GenerateFastPathFuncForEnumerable(typeCompilableName, serializeMethodName, canBeNull, isArray: false, collectionValueTypeMetadata);
+                            break;
+                        case CollectionType.Dictionary:
+                            serializeFuncSource = GenerateFastPathFuncForDictionary(typeCompilableName, serializeMethodName, canBeNull, collectionKeyTypeMetadata, collectionValueTypeMetadata);
+                            break;
+                        default:
+                            serializeFuncSource = null;
+                            break;
+                    }
+
+                    serializeFuncNamedArg = $"serializeFunc: {serializeMethodName}";
+                }
 
-                CollectionType collectionType = typeMetadata.CollectionType;
                 string collectionTypeInfoValue = collectionType switch
                 {
-                    CollectionType.Array => $"{JsonMetadataServicesClassName}.CreateArrayInfo<{valueTypeCompilableName}>({OptionsInstanceVariableName}, {valueTypeMetadataPropertyName}, {numberHandlingArg})",
-                    CollectionType.List => $"{JsonMetadataServicesClassName}.CreateListInfo<{typeCompilableName}, {valueTypeCompilableName}>({OptionsInstanceVariableName}, () => new System.Collections.Generic.List<{valueTypeCompilableName}>(), {valueTypeMetadataPropertyName}, {numberHandlingArg})",
-                    CollectionType.Dictionary => $"{JsonMetadataServicesClassName}.CreateDictionaryInfo<{typeCompilableName}, {keyTypeCompilableName!}, {valueTypeCompilableName}>({OptionsInstanceVariableName}, () => new System.Collections.Generic.Dictionary<{keyTypeCompilableName}, {valueTypeCompilableName}>(), {keyTypeMetadataPropertyName!}, {valueTypeMetadataPropertyName}, {numberHandlingArg})",
+                    CollectionType.Array => $"{JsonMetadataServicesTypeRef}.CreateArrayInfo<{valueTypeCompilableName}>({OptionsInstanceVariableName}, {valueTypeMetadataPropertyName}, {numberHandlingArg}, {serializeFuncNamedArg})",
+                    CollectionType.List => $"{JsonMetadataServicesTypeRef}.CreateListInfo<{typeCompilableName}, {valueTypeCompilableName}>({OptionsInstanceVariableName}, () => new {ListTypeRef}<{valueTypeCompilableName}>(), {valueTypeMetadataPropertyName}, {numberHandlingArg}, {serializeFuncNamedArg})",
+                    CollectionType.Dictionary => $"{JsonMetadataServicesTypeRef}.CreateDictionaryInfo<{typeCompilableName}, {keyTypeCompilableName!}, {valueTypeCompilableName}>({OptionsInstanceVariableName}, () => new {DictionaryTypeRef}<{keyTypeCompilableName}, {valueTypeCompilableName}>(), {keyTypeMetadataPropertyName!}, {valueTypeMetadataPropertyName}, {numberHandlingArg}, {serializeFuncNamedArg})",
                     _ => throw new NotSupportedException()
                 };
 
                 string metadataInitSource = @$"_{typeFriendlyName} = {collectionTypeInfoValue};";
-                return GenerateForType(typeMetadata, metadataInitSource);
+
+                return GenerateForType(typeGenerationSpec, metadataInitSource, serializeFuncSource);
+            }
+
+            private string GenerateFastPathFuncForEnumerable(string typeInfoRef, string serializeMethodName, bool canBeNull, bool isArray, TypeGenerationSpec valueTypeGenerationSpec)
+            {
+                string? writerMethodToCall = GetWriterMethod(valueTypeGenerationSpec.Type);
+                string valueToWrite = $"{ValueVarName}[i]";
+                string lengthPropName = isArray ? "Length" : "Count";
+
+                string elementSerializationLogic;
+                if (writerMethodToCall != null)
+                {
+                    elementSerializationLogic = $"{writerMethodToCall}Value({valueToWrite});";
+                }
+                else
+                {
+                    elementSerializationLogic = GetSerializeLogicForNonPrimitiveType(valueTypeGenerationSpec.TypeInfoPropertyName, valueToWrite, valueTypeGenerationSpec.GenerateSerializationLogic);
+                }
+
+                string serializationLogic = $@"{WriterVarName}.WriteStartArray();
+
+    for (int i = 0; i < {ValueVarName}.{lengthPropName}; i++)
+    {{
+        {elementSerializationLogic}
+    }}
+
+    {WriterVarName}.WriteEndArray();";
+
+                return GenerateFastPathFuncForType(serializeMethodName, typeInfoRef, serializationLogic, canBeNull);
             }
 
-            private string GenerateForObject(TypeMetadata typeMetadata)
+            private string GenerateFastPathFuncForDictionary(
+                string typeInfoRef,
+                string serializeMethodName,
+                bool canBeNull,
+                TypeGenerationSpec keyTypeGenerationSpec,
+                TypeGenerationSpec valueTypeGenerationSpec)
             {
-                string typeCompilableName = typeMetadata.CompilableName;
-                string typeFriendlyName = typeMetadata.FriendlyName;
+                const string pairVarName = "pair";
+                string keyToWrite = $"{pairVarName}.Key";
+                string valueToWrite = $"{pairVarName}.Value";
+
+                string? writerMethodToCall = GetWriterMethod(valueTypeGenerationSpec.Type);
+                string elementSerializationLogic;
+
+                if (writerMethodToCall != null)
+                {
+                    elementSerializationLogic = $"{writerMethodToCall}({keyToWrite}, {valueToWrite});";
+                }
+                else
+                {
+                    elementSerializationLogic = $@"{WriterVarName}.WritePropertyName({keyToWrite});
+        {GetSerializeLogicForNonPrimitiveType(valueTypeGenerationSpec.TypeInfoPropertyName, valueToWrite, valueTypeGenerationSpec.GenerateSerializationLogic)}";
+                }
+
+                string serializationLogic = $@"{WriterVarName}.WriteStartObject();
+
+    foreach ({KeyValuePairTypeRef}<{keyTypeGenerationSpec.TypeRef}, {valueTypeGenerationSpec.TypeRef}> {pairVarName} in {ValueVarName})
+    {{
+        {elementSerializationLogic}
+    }}
+
+    {WriterVarName}.WriteEndObject();";
+
+                return GenerateFastPathFuncForType(serializeMethodName, typeInfoRef, serializationLogic, canBeNull);
+            }
+
+            private string GenerateForObject(TypeGenerationSpec typeMetadata)
+            {
+                string typeCompilableName = typeMetadata.TypeRef;
+                string typeFriendlyName = typeMetadata.TypeInfoPropertyName;
 
                 string createObjectFuncTypeArg = typeMetadata.ConstructionStrategy == ObjectConstructionStrategy.ParameterlessConstructor
-                    ? $"createObjectFunc: static () => new {typeMetadata.CompilableName}()"
+                    ? $"createObjectFunc: static () => new {typeMetadata.TypeRef}()"
                     : "createObjectFunc: null";
 
-                List<PropertyMetadata>? properties = typeMetadata.PropertiesMetadata;
+                string propInitMethodName = $"{typeFriendlyName}{PropInitMethodNameSuffix}";
+                string? propMetadataInitFuncSource = null;
+                string propMetadataInitFuncNamedArg;
 
-                StringBuilder sb = new();
+                string serializeMethodName = $"{typeFriendlyName}{SerializeMethodNameSuffix}";
+                string? serializeFuncSource = null;
+                string serializeFuncNamedArg;
 
-                sb.Append($@"JsonTypeInfo<{typeCompilableName}> objectInfo = {JsonMetadataServicesClassName}.CreateObjectInfo<{typeCompilableName}>();
-                    _{typeFriendlyName} = objectInfo;
-");
+                List<PropertyGenerationSpec>? properties = typeMetadata.PropertiesMetadata;
 
-                string propInitFuncVarName = $"{typeFriendlyName}{PropInitFuncVarName}";
+                if (typeMetadata.GenerateMetadata)
+                {
+                    propMetadataInitFuncSource = GeneratePropMetadataInitFunc(typeMetadata.IsValueType, propInitMethodName, properties);
+                    propMetadataInitFuncNamedArg = $@"propInitFunc: {propInitMethodName}";
+                }
+                else
+                {
+                    propMetadataInitFuncNamedArg = @"propInitFunc: null";
+                }
 
-                sb.Append($@"
-                    {JsonMetadataServicesClassName}.InitializeObjectInfo(
-                        objectInfo,
-                        {OptionsInstanceVariableName},
-                        {createObjectFuncTypeArg},
-                        {propInitFuncVarName},
-                        {GetNumberHandlingAsStr(typeMetadata.NumberHandling)});");
+                if (typeMetadata.GenerateSerializationLogic)
+                {
+                    serializeFuncSource = GenerateFastPathFuncForObject(typeCompilableName, serializeMethodName, typeMetadata.CanBeNull, properties);
+                    serializeFuncNamedArg = $@"serializeFunc: {serializeMethodName}";
+                }
+                else
+                {
+                    serializeFuncNamedArg = @"serializeFunc: null";
+                }
+
+                string objectInfoInitSource = $@"{JsonTypeInfoTypeRef}<{typeCompilableName}> objectInfo = {JsonMetadataServicesTypeRef}.CreateObjectInfo<{typeCompilableName}>(
+                    {OptionsInstanceVariableName},
+                    {createObjectFuncTypeArg},
+                    {propMetadataInitFuncNamedArg},
+                    {GetNumberHandlingAsStr(typeMetadata.NumberHandling)},
+                    {serializeFuncNamedArg});
 
-                string metadataInitSource = sb.ToString();
-                string? propInitFuncSource = GeneratePropMetadataInitFunc(typeMetadata.IsValueType, propInitFuncVarName, properties);
+                    _{typeFriendlyName} = objectInfo;";
 
-                return GenerateForType(typeMetadata, metadataInitSource, propInitFuncSource);
+                string additionalSource;
+                if (propMetadataInitFuncSource == null || serializeFuncSource == null)
+                {
+                    additionalSource = propMetadataInitFuncSource ?? serializeFuncSource;
+                }
+                else
+                {
+                    additionalSource = @$"{propMetadataInitFuncSource}{serializeFuncSource}";
+                }
+
+                return GenerateForType(typeMetadata, objectInfoInitSource, additionalSource);
             }
 
             private string GeneratePropMetadataInitFunc(
                 bool declaringTypeIsValueType,
-                string propInitFuncVarName,
-                List<PropertyMetadata>? properties)
+                string propInitMethodName,
+                List<PropertyGenerationSpec>? properties)
             {
                 const string PropVarName = "properties";
                 const string JsonContextVarName = "jsonContext";
-                const string JsonPropertyInfoTypeName = "JsonPropertyInfo";
 
                 string propertyArrayInstantiationValue = properties == null
-                    ? $"System.Array.Empty<{JsonPropertyInfoTypeName}>()"
-                    : $"new {JsonPropertyInfoTypeName}[{properties.Count}]";
+                    ? $"{ArrayTypeRef}.Empty<{JsonPropertyInfoTypeRef}>()"
+                    : $"new {JsonPropertyInfoTypeRef}[{properties.Count}]";
+
+                string contextTypeRef = _currentContext.ContextTypeRef;
 
                 StringBuilder sb = new();
 
                 sb.Append($@"
-        private static {JsonPropertyInfoTypeName}[] {propInitFuncVarName}(JsonSerializerContext context)
-        {{
-            JsonContext {JsonContextVarName} = (JsonContext)context;
-            JsonSerializerOptions options = context.Options;
 
-            {JsonPropertyInfoTypeName}[] {PropVarName} = {propertyArrayInstantiationValue};
+private static {JsonPropertyInfoTypeRef}[] {propInitMethodName}({JsonSerializerContextTypeRef} context)
+{{
+    {contextTypeRef} {JsonContextVarName} = ({contextTypeRef})context;
+    {JsonSerializerOptionsTypeRef} options = context.Options;
+
+    {JsonPropertyInfoTypeRef}[] {PropVarName} = {propertyArrayInstantiationValue};
 ");
 
                 if (properties != null)
                 {
                     for (int i = 0; i < properties.Count; i++)
                     {
-                        PropertyMetadata memberMetadata = properties[i];
+                        PropertyGenerationSpec memberMetadata = properties[i];
 
-                        TypeMetadata memberTypeMetadata = memberMetadata.TypeMetadata;
+                        TypeGenerationSpec memberTypeMetadata = memberMetadata.TypeGenerationSpec;
 
                         string clrPropertyName = memberMetadata.ClrName;
 
-                        string declaringTypeCompilableName = memberMetadata.DeclaringTypeCompilableName;
+                        string declaringTypeCompilableName = memberMetadata.DeclaringTypeRef;
 
                         string memberTypeFriendlyName = memberTypeMetadata.ClassType == ClassType.TypeUnsupportedBySourceGen
                             ? "null"
-                            : $"{JsonContextVarName}.{memberTypeMetadata.FriendlyName}";
+                            : $"{JsonContextVarName}.{memberTypeMetadata.TypeInfoPropertyName}";
 
                         string typeTypeInfoNamedArg = $"propertyTypeInfo: {memberTypeFriendlyName}";
 
@@ -383,15 +578,15 @@ namespace System.Text.Json.SourceGeneration
                             ? @$"jsonPropertyName: ""{memberMetadata.JsonPropertyName}"""
                             : "jsonPropertyName: null";
 
-                        string getterNamedArg = memberMetadata.HasGetter
+                        string getterNamedArg = memberMetadata.CanUseGetter
                             ? $"getter: static (obj) => {{ return (({declaringTypeCompilableName})obj).{clrPropertyName}; }}"
                             : "getter: null";
 
                         string setterNamedArg;
-                        if (memberMetadata.HasSetter)
+                        if (memberMetadata.CanUseSetter)
                         {
                             string propMutation = declaringTypeIsValueType
-                                ? @$"{{ Unsafe.Unbox<{declaringTypeCompilableName}>(obj).{clrPropertyName} = value; }}"
+                                ? @$"{{ {UnsafeTypeRef}.Unbox<{declaringTypeCompilableName}>(obj).{clrPropertyName} = value; }}"
                                 : $@"{{ (({declaringTypeCompilableName})obj).{clrPropertyName} = value; }}";
 
                             setterNamedArg = $"setter: static (obj, value) => {propMutation}";
@@ -401,7 +596,7 @@ namespace System.Text.Json.SourceGeneration
                             setterNamedArg = "setter: null";
                         }
 
-                        JsonIgnoreCondition? ignoreCondition = memberMetadata.IgnoreCondition;
+                        JsonIgnoreCondition? ignoreCondition = memberMetadata.DefaultIgnoreCondition;
                         string ignoreConditionNamedArg = ignoreCondition.HasValue
                             ? $"ignoreCondition: JsonIgnoreCondition.{ignoreCondition.Value}"
                             : "ignoreCondition: default";
@@ -410,270 +605,442 @@ namespace System.Text.Json.SourceGeneration
                             ? "converter: null"
                             : $"converter: {memberMetadata.ConverterInstantiationLogic}";
 
-                        string memberTypeCompilableName = memberTypeMetadata.CompilableName;
+                        string memberTypeCompilableName = memberTypeMetadata.TypeRef;
 
                         sb.Append($@"
-            {PropVarName}[{i}] = {JsonMetadataServicesClassName}.CreatePropertyInfo<{memberTypeCompilableName}>(
-                options,
-                isProperty: {memberMetadata.IsProperty.ToString().ToLowerInvariant()},
-                declaringType: typeof({memberMetadata.DeclaringTypeCompilableName}),
-                {typeTypeInfoNamedArg},
-                {converterNamedArg},
-                {getterNamedArg},
-                {setterNamedArg},
-                {ignoreConditionNamedArg},
-                numberHandling: {GetNumberHandlingAsStr(memberMetadata.NumberHandling)},
-                propertyName: ""{clrPropertyName}"",
-                {jsonPropertyNameNamedArg});
-            ");
+    {PropVarName}[{i}] = {JsonMetadataServicesTypeRef}.CreatePropertyInfo<{memberTypeCompilableName}>(
+        options,
+        isProperty: {memberMetadata.IsProperty.ToString().ToLowerInvariant()},
+        declaringType: typeof({memberMetadata.DeclaringTypeRef}),
+        {typeTypeInfoNamedArg},
+        {converterNamedArg},
+        {getterNamedArg},
+        {setterNamedArg},
+        {ignoreConditionNamedArg},
+        numberHandling: {GetNumberHandlingAsStr(memberMetadata.NumberHandling)},
+        propertyName: ""{clrPropertyName}"",
+        {jsonPropertyNameNamedArg});
+    ");
                     }
                 }
 
                 sb.Append(@$"
-            return {PropVarName};
-        }}");
+    return {PropVarName};
+}}");
 
                 return sb.ToString();
             }
 
-            private string GenerateForType(TypeMetadata typeMetadata, string metadataInitSource, string? additionalSource = null)
+            private string GenerateFastPathFuncForObject(
+                string typeInfoTypeRef,
+                string serializeMethodName,
+                bool canBeNull,
+                List<PropertyGenerationSpec>? properties)
+            {
+                JsonSerializerOptionsAttribute options = _currentContext.SerializerOptions;
+
+                // Add the property names to the context-wide cache; we'll generate the source to initialize them at the end of generation.
+                string[] runtimePropNames = GetRuntimePropNames(properties, options.NamingPolicy);
+                _currentContext.RuntimePropertyNames.UnionWith(runtimePropNames);
+
+                StringBuilder sb = new();
+
+                // Begin method definition
+                sb.Append($@"{WriterVarName}.WriteStartObject();");
+
+                if (properties != null)
+                {
+                    // Provide generation logic for each prop.
+                    for (int i = 0; i < properties.Count; i++)
+                    {
+                        PropertyGenerationSpec propertySpec = properties[i];
+                        TypeGenerationSpec propertyTypeSpec = propertySpec.TypeGenerationSpec;
+
+                        if (propertyTypeSpec.ClassType == ClassType.TypeUnsupportedBySourceGen)
+                        {
+                            continue;
+                        }
+
+                        if (propertySpec.IsReadOnly)
+                        {
+                            if (propertySpec.IsProperty)
+                            {
+                                if (options.IgnoreReadOnlyProperties)
+                                {
+                                    continue;
+                                }
+                            }
+                            else if (options.IgnoreReadOnlyFields)
+                            {
+                                continue;
+                            }
+                        }
+
+                        if (!propertySpec.IsProperty && !propertySpec.HasJsonInclude && !options.IncludeFields)
+                        {
+                            continue;
+                        }
+
+                        Type propertyType = propertyTypeSpec.Type;
+                        string propName = $"{runtimePropNames[i]}PropName";
+                        string propValue = $"{ValueVarName}.{propertySpec.ClrName}";
+                        string methodArgs = $"{propName}, {propValue}";
+
+                        string? methodToCall = GetWriterMethod(propertyType);
+
+                        if (propertyType == _generationSpec.CharType)
+                        {
+                            methodArgs = $"{methodArgs}.ToString()";
+                        }
+
+                        string serializationLogic;
+
+                        if (methodToCall != null)
+                        {
+                            serializationLogic = $@"
+        {methodToCall}({methodArgs});";
+                        }
+                        else
+                        {
+                            serializationLogic = $@"
+        {WriterVarName}.WritePropertyName({propName});
+        {GetSerializeLogicForNonPrimitiveType(propertyTypeSpec.TypeInfoPropertyName, propValue, propertyTypeSpec.GenerateSerializationLogic)}";
+                        }
+
+                        JsonIgnoreCondition ignoreCondition = propertySpec.DefaultIgnoreCondition ?? options.DefaultIgnoreCondition;
+                        DefaultCheckType defaultCheckType;
+                        bool typeCanBeNull = propertyTypeSpec.CanBeNull;
+
+                        switch (ignoreCondition)
+                        {
+                            case JsonIgnoreCondition.WhenWritingNull:
+                                defaultCheckType = typeCanBeNull ? DefaultCheckType.Null : DefaultCheckType.None;
+                                break;
+                            case JsonIgnoreCondition.WhenWritingDefault:
+                                defaultCheckType = typeCanBeNull ? DefaultCheckType.Null : DefaultCheckType.Default;
+                                break;
+                            default:
+                                defaultCheckType = DefaultCheckType.None;
+                                break;
+                        }
+
+                        sb.Append(WrapSerializationLogicInDefaultCheckIfRequired(serializationLogic, propValue, defaultCheckType));
+                    }
+                }
+
+                // End method definition
+                sb.Append($@"
+
+        {WriterVarName}.WriteEndObject();");
+
+                return GenerateFastPathFuncForType(serializeMethodName, typeInfoTypeRef, sb.ToString(), canBeNull);
+            }
+
+            private string? GetWriterMethod(Type type)
+            {
+                string? method;
+                if (_generationSpec.IsStringBasedType(type))
+                {
+                    method = $"{WriterVarName}.WriteString";
+                }
+                else if (type == _generationSpec.BooleanType)
+                {
+                    method = $"{WriterVarName}.WriteBoolean";
+                }
+                else if (type == _generationSpec.ByteArrayType)
+                {
+                    method = $"{WriterVarName}.WriteBase64String";
+                }
+                else if (type == _generationSpec.CharType)
+                {
+                    method = $"{WriterVarName}.WriteString";
+                }
+                else if (_generationSpec.IsNumberType(type))
+                {
+                    method = $"{WriterVarName}.WriteNumber";
+                }
+                else
+                {
+                    method = null;
+                }
+
+                return method;
+            }
+
+            private string GenerateFastPathFuncForType(string serializeMethodName, string typeInfoTypeRef, string serializationLogic, bool canBeNull)
+            {
+                return $@"
+
+private static void {serializeMethodName}({Utf8JsonWriterTypeRef} {WriterVarName}, {typeInfoTypeRef} {ValueVarName})
+{{
+    {GetEarlyNullCheckSource(canBeNull)}
+    {serializationLogic}
+}}";
+            }
+
+            private string GetEarlyNullCheckSource(bool canBeNull)
+            {
+                return canBeNull
+                    ? $@"if ({ValueVarName} == null)
+    {{
+        {WriterVarName}.WriteNullValue();
+        return;
+    }}
+"
+                    : null;
+            }
+
+            private string GetSerializeLogicForNonPrimitiveType(string typeInfoPropertyName, string valueToWrite, bool serializationLogicGenerated)
+            {
+                string typeInfoRef = $"{_currentContext.ContextTypeRef}.Default.{typeInfoPropertyName}";
+
+                if (serializationLogicGenerated)
+                {
+                    return $"{typeInfoPropertyName}{SerializeMethodNameSuffix}({WriterVarName}, {valueToWrite});";
+                }
+
+                return $"{JsonSerializerTypeRef}.Serialize({WriterVarName}, {valueToWrite}, {typeInfoRef});";
+            }
+
+            private enum DefaultCheckType
+            {
+                None,
+                Null,
+                Default,
+            }
+
+            private string WrapSerializationLogicInDefaultCheckIfRequired(string serializationLogic, string propValue, DefaultCheckType defaultCheckType)
+            {
+                if (defaultCheckType == DefaultCheckType.None)
+                {
+                    return serializationLogic;
+                }
+
+                string defaultLiteral = defaultCheckType == DefaultCheckType.Null ? "null" : "default";
+                return $@"
+        if ({propValue} != {defaultLiteral})
+        {{{serializationLogic}
+        }}";
+            }
+
+            private string[] GetRuntimePropNames(List<PropertyGenerationSpec>? properties, JsonKnownNamingPolicy namingPolicy)
+            {
+                if (properties == null)
+                {
+                    return Array.Empty<string>();
+                }
+
+                int propCount = properties.Count;
+                string[] runtimePropNames = new string[propCount];
+
+                // Compute JsonEncodedText values to represent each property name. This gives the best throughput performance
+                for (int i = 0; i < propCount; i++)
+                {
+                    PropertyGenerationSpec propertySpec = properties[i];
+
+                    string propName = DetermineRuntimePropName(propertySpec.ClrName, propertySpec.JsonPropertyName, namingPolicy);
+                    Debug.Assert(propName != null);
+
+                    runtimePropNames[i] = propName;
+                }
+
+                return runtimePropNames;
+            }
+
+            private string DetermineRuntimePropName(string clrPropName, string? jsonPropName, JsonKnownNamingPolicy namingPolicy)
             {
-                string typeCompilableName = typeMetadata.CompilableName;
-                string typeFriendlyName = typeMetadata.FriendlyName;
+                string runtimePropName;
 
-                return @$"{GetUsingStatementsString(typeMetadata)}
+                if (jsonPropName != null)
+                {
+                    runtimePropName = jsonPropName;
+                }
+                else if (namingPolicy == JsonKnownNamingPolicy.BuiltInCamelCase)
+                {
+                    runtimePropName = JsonNamingPolicy.CamelCase.ConvertName(clrPropName);
+                }
+                else
+                {
+                    runtimePropName = clrPropName;
+                }
+
+                return runtimePropName;
+            }
+
+            private string GenerateForType(TypeGenerationSpec typeMetadata, string metadataInitSource, string? additionalSource = null)
+            {
+                string typeCompilableName = typeMetadata.TypeRef;
+                string typeFriendlyName = typeMetadata.TypeInfoPropertyName;
+                string typeInfoPropertyTypeRef = $"{JsonTypeInfoTypeRef}<{typeCompilableName}>";
 
-namespace {_generationNamespace}
+                return @$"private {typeInfoPropertyTypeRef} _{typeFriendlyName};
+public {typeInfoPropertyTypeRef} {typeFriendlyName}
 {{
-    {JsonContextDeclarationSource}
+    get
     {{
-        private JsonTypeInfo<{typeCompilableName}> _{typeFriendlyName};
-        public JsonTypeInfo<{typeCompilableName}> {typeFriendlyName}
+        if (_{typeFriendlyName} == null)
         {{
-            get
-            {{
-                if (_{typeFriendlyName} == null)
-                {{
-                    {WrapWithCheckForCustomConverterIfRequired(metadataInitSource, typeCompilableName, typeFriendlyName, GetNumberHandlingAsStr(typeMetadata.NumberHandling))}
-                }}
+            {WrapWithCheckForCustomConverterIfRequired(metadataInitSource, typeCompilableName, typeFriendlyName, GetNumberHandlingAsStr(typeMetadata.NumberHandling))}
+        }}
 
-                return _{typeFriendlyName};
-            }}
-        }}{additionalSource}
+        return _{typeFriendlyName};
     }}
-}}
-";
+}}{additionalSource}";
             }
 
             private string WrapWithCheckForCustomConverterIfRequired(string source, string typeCompilableName, string typeFriendlyName, string numberHandlingNamedArg)
             {
-                if (!_honorRuntimeProvidedCustomConverters)
+                if (_currentContext.SerializerOptions.IgnoreRuntimeCustomConverters)
                 {
                     return source;
                 }
 
-                return @$"JsonConverter customConverter;
-                    if ({OptionsInstanceVariableName}.Converters.Count > 0 && (customConverter = {RuntimeCustomConverterFetchingMethodName}(typeof({typeCompilableName}))) != null)
-                    {{
-                        _{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, customConverter);
-                    }}
-                    else
-                    {{
-                        {source.Replace(Environment.NewLine, $"{Environment.NewLine}    ")}
-                    }}";
+                return @$"{JsonConverterTypeRef} customConverter;
+                if ({OptionsInstanceVariableName}.Converters.Count > 0 && (customConverter = {RuntimeCustomConverterFetchingMethodName}(typeof({typeCompilableName}))) != null)
+                {{
+                    _{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, customConverter);
+                }}
+                else
+                {{
+                    {IndentSource(source, numIndentations: 1)}
+                }}";
             }
 
-            private string GetBaseJsonContextImplementation()
+            private string GetRootJsonContextImplementation()
             {
+                string contextTypeRef = _currentContext.ContextTypeRef;
+                string contextTypeName = _currentContext.ContextType.Name;
+
                 StringBuilder sb = new();
-                sb.Append(@$"using System.Text.Json;
-using System.Text.Json.Serialization;
 
-namespace {_generationNamespace}
-{{
-    {JsonContextDeclarationSource}
-    {{
-        private static JsonContext s_default;
-        public static JsonContext Default => s_default ??= new JsonContext(new JsonSerializerOptions());
+                sb.Append(@$"{GetLogicForDefaultSerializerOptionsInit()}
 
-        public JsonContext() : base(null)
-        {{
-        }}
+private static {contextTypeRef} {DefaultContextBackingStaticVarName};
+public static {contextTypeRef} Default => {DefaultContextBackingStaticVarName} ??= new {contextTypeRef}(new {JsonSerializerOptionsTypeRef}({DefaultOptionsStaticVarName}));
 
-        public JsonContext(JsonSerializerOptions options) : base(options)
-        {{
-        }}
+public {contextTypeName}() : base(null, {DefaultOptionsStaticVarName})
+{{
+}}
 
-        {GetFetchLogicForRuntimeSpecifiedCustomConverter()}
-    }}
+public {contextTypeName}({JsonSerializerOptionsTypeRef} options) : base(options, {DefaultOptionsStaticVarName})
+{{
 }}
-");
+
+{GetFetchLogicForRuntimeSpecifiedCustomConverter()}");
 
                 return sb.ToString();
             }
 
+            private string GetLogicForDefaultSerializerOptionsInit()
+            {
+                JsonSerializerOptionsAttribute options = _currentContext.SerializerOptions;
+
+                string? namingPolicyInit = options.NamingPolicy == JsonKnownNamingPolicy.BuiltInCamelCase
+                    ? $@"
+            PropertyNamingPolicy = {JsonNamingPolicyTypeRef}.CamelCase"
+                    : null;
+
+                return $@"
+private static {JsonSerializerOptionsTypeRef} {DefaultOptionsStaticVarName} {{ get; }} = new {JsonSerializerOptionsTypeRef}()
+{{
+    DefaultIgnoreCondition = {JsonIgnoreConditionTypeRef}.{options.DefaultIgnoreCondition},
+    IgnoreReadOnlyFields = {options.IgnoreReadOnlyFields.ToString().ToLowerInvariant()},
+    IgnoreReadOnlyProperties = {options.IgnoreReadOnlyProperties.ToString().ToLowerInvariant()},
+    IncludeFields = {options.IncludeFields.ToString().ToLowerInvariant()},
+    WriteIndented = {options.WriteIndented.ToString().ToLowerInvariant()},{namingPolicyInit}
+}};";
+            }
+
             private string GetFetchLogicForRuntimeSpecifiedCustomConverter()
             {
-                if (!_honorRuntimeProvidedCustomConverters)
+                if (_currentContext.SerializerOptions.IgnoreRuntimeCustomConverters)
                 {
                     return "";
                 }
 
                 // TODO (https://github.com/dotnet/runtime/issues/52218): use a dictionary if count > ~15.
-                return @$"private JsonConverter {RuntimeCustomConverterFetchingMethodName}(System.Type type)
-        {{
-            System.Collections.Generic.IList<JsonConverter> converters = {OptionsInstanceVariableName}.Converters;
+                return @$"private {JsonConverterTypeRef} {RuntimeCustomConverterFetchingMethodName}({TypeTypeRef} type)
+{{
+    {IListTypeRef}<{JsonConverterTypeRef}> converters = {OptionsInstanceVariableName}.Converters;
 
-            for (int i = 0; i < converters.Count; i++)
-            {{
-                JsonConverter converter = converters[i];
+    for (int i = 0; i < converters.Count; i++)
+    {{
+        {JsonConverterTypeRef} converter = converters[i];
 
-                if (converter.CanConvert(type))
+        if (converter.CanConvert(type))
+        {{
+            if (converter is {JsonConverterFactoryTypeRef} factory)
+            {{
+                converter = factory.CreateConverter(type, {OptionsInstanceVariableName});
+                if (converter == null || converter is {JsonConverterFactoryTypeRef})
                 {{
-                    if (converter is JsonConverterFactory factory)
-                    {{
-                        converter = factory.CreateConverter(type, {OptionsInstanceVariableName});
-                        if (converter == null || converter is JsonConverterFactory)
-                        {{
-                            throw new System.InvalidOperationException($""The converter '{{factory.GetType()}}' cannot return null or a JsonConverterFactory instance."");
-                        }}
-                    }}
-
-                    return converter;
+                    throw new {InvalidOperationExceptionTypeRef}($""The converter '{{factory.GetType()}}' cannot return null or a JsonConverterFactory instance."");
                 }}
             }}
 
-            return null;
-        }}";
+            return converter;
+        }}
+    }}
+
+    return null;
+}}";
             }
 
             private string GetGetTypeInfoImplementation()
             {
                 StringBuilder sb = new();
 
-                HashSet<string> usingStatements = new();
-
-                foreach (TypeMetadata metadata in _rootSerializableTypes.Values)
-                {
-                    usingStatements.UnionWith(GetUsingStatements(metadata));
-                }
-
-                sb.Append(@$"{GetUsingStatementsString(usingStatements)}
-
-namespace {_generationNamespace}
-{{
-    {JsonContextDeclarationSource}
-    {{
-        public override JsonTypeInfo GetTypeInfo(System.Type type)
-        {{");
+                sb.Append(@$"public override {JsonTypeInfoTypeRef} GetTypeInfo({TypeTypeRef} type)
+{{");
 
                 // TODO (https://github.com/dotnet/runtime/issues/52218): Make this Dictionary-lookup-based if root-serializable type count > 64.
-                foreach (TypeMetadata metadata in _rootSerializableTypes.Values)
+                foreach (TypeGenerationSpec metadata in _currentContext.RootSerializableTypes)
                 {
                     if (metadata.ClassType != ClassType.TypeUnsupportedBySourceGen)
                     {
                         sb.Append($@"
-            if (type == typeof({metadata.Type.GetUniqueCompilableTypeName()}))
-            {{
-                return this.{metadata.FriendlyName};
-            }}
+    if (type == typeof({metadata.TypeRef}))
+    {{
+        return this.{metadata.TypeInfoPropertyName};
+    }}
 ");
                     }
                 }
 
                 sb.Append(@"
-            return null!;
-        }
-    }
-}
-");
+    return null!;
+}");
 
                 return sb.ToString();
             }
 
-            private static string GetUsingStatementsString(TypeMetadata typeMetadata)
+            private string GetPropertyNameInitialization()
             {
-                HashSet<string> usingStatements = GetUsingStatements(typeMetadata);
-                return GetUsingStatementsString(usingStatements);
-            }
+                // Ensure metadata for types has already occured.
+                Debug.Assert(!(
+                    _currentContext.TypesWithMetadataGenerated.Count == 0
+                    && _currentContext.RuntimePropertyNames.Count > 0));
 
-            private static string GetUsingStatementsString(HashSet<string> usingStatements)
-            {
-                string[] usingsArr = usingStatements.ToArray();
-                Array.Sort(usingsArr);
-                return string.Join("\n", usingsArr);
-            }
-
-            private static HashSet<string> GetUsingStatements(TypeMetadata typeMetadata)
-            {
-                HashSet<string> usingStatements = new();
-
-                // Add library usings.
-                usingStatements.Add(FormatAsUsingStatement("System.Runtime.CompilerServices"));
-                usingStatements.Add(FormatAsUsingStatement("System.Text.Json"));
-                usingStatements.Add(FormatAsUsingStatement("System.Text.Json.Serialization"));
-                usingStatements.Add(FormatAsUsingStatement("System.Text.Json.Serialization.Metadata"));
-
-                // Add imports to root type.
-                usingStatements.Add(FormatAsUsingStatement(typeMetadata.Type.Namespace));
-
-                switch (typeMetadata.ClassType)
-                {
-                    case ClassType.Nullable:
-                        {
-                            AddUsingStatementsForType(typeMetadata.NullableUnderlyingTypeMetadata!);
-                        }
-                        break;
-                    case ClassType.Enumerable:
-                        {
-                            AddUsingStatementsForType(typeMetadata.CollectionValueTypeMetadata);
-                        }
-                        break;
-                    case ClassType.Dictionary:
-                        {
-                            AddUsingStatementsForType(typeMetadata.CollectionKeyTypeMetadata);
-                            AddUsingStatementsForType(typeMetadata.CollectionValueTypeMetadata);
-                        }
-                        break;
-                    case ClassType.Object:
-                        {
-                            if (typeMetadata.PropertiesMetadata != null)
-                            {
-                                foreach (PropertyMetadata metadata in typeMetadata.PropertiesMetadata)
-                                {
-                                    AddUsingStatementsForType(metadata.TypeMetadata);
-                                }
-                            }
-                        }
-                        break;
-                    default:
-                        break;
-                }
+                StringBuilder sb = new();
 
-                void AddUsingStatementsForType(TypeMetadata typeMetadata)
+                foreach (string propName in _currentContext.RuntimePropertyNames)
                 {
-                    usingStatements.Add(FormatAsUsingStatement(typeMetadata.Type.Namespace));
-
-                    if (typeMetadata.CollectionKeyTypeMetadata != null)
-                    {
-                        Debug.Assert(typeMetadata.CollectionValueTypeMetadata != null);
-                        usingStatements.Add(FormatAsUsingStatement(typeMetadata.CollectionKeyTypeMetadata.Type.Namespace));
-                    }
-
-                    if (typeMetadata.CollectionValueTypeMetadata != null)
-                    {
-                        usingStatements.Add(FormatAsUsingStatement(typeMetadata.CollectionValueTypeMetadata.Type.Namespace));
-                    }
+                    sb.Append($@"
+private static {JsonEncodedTextTypeRef} {propName}PropName = {JsonEncodedTextTypeRef}.Encode(""{propName}"");");
                 }
 
-                return usingStatements;
+                return sb.ToString();
             }
 
-            private static string FormatAsUsingStatement(string @namespace) => $"using {@namespace};";
+            private static string IndentSource(string source, int numIndentations)
+            {
+                Debug.Assert(numIndentations >= 1);
+                return source.Replace(Environment.NewLine, $"{Environment.NewLine}{new string(' ', 4 * numIndentations)}"); // 4 spaces per indentation.
+            }
 
             private static string GetNumberHandlingAsStr(JsonNumberHandling? numberHandling) =>
                  numberHandling.HasValue
-                    ? $"(JsonNumberHandling){(int)numberHandling.Value}"
+                    ? $"({JsonNumberHandlingTypeRef}){(int)numberHandling.Value}"
                     : "default";
 
             private static string GetCreateValueInfoMethodRef(string typeCompilableName) => $"{CreateValueInfoMethodName}<{typeCompilableName}>";
index 86a7043..b909e1e 100644 (file)
@@ -11,6 +11,8 @@ using System.Text.Json.Serialization;
 using System.Text.Json.SourceGeneration.Reflection;
 using System.Linq;
 using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
 
 namespace System.Text.Json.SourceGeneration
 {
@@ -32,7 +34,7 @@ namespace System.Text.Json.SourceGeneration
 
             private const string JsonPropertyNameAttributeFullName = "System.Text.Json.Serialization.JsonPropertyNameAttribute";
 
-            private readonly Compilation _compilation;
+            private readonly GeneratorExecutionContext _executionContext;
 
             private readonly MetadataLoadContextInternal _metadataLoadContext;
 
@@ -46,6 +48,7 @@ namespace System.Text.Json.SourceGeneration
             private readonly Type _dateTimeType;
             private readonly Type _dateTimeOffsetType;
             private readonly Type _guidType;
+            private readonly Type _nullableOfTType;
             private readonly Type _stringType;
             private readonly Type _uriType;
             private readonly Type _versionType;
@@ -57,12 +60,20 @@ namespace System.Text.Json.SourceGeneration
             /// <summary>
             /// Type information for member types in input object graphs.
             /// </summary>
-            private readonly Dictionary<Type, TypeMetadata> _typeMetadataCache = new();
+            private readonly Dictionary<Type, TypeGenerationSpec> _typeGenerationSpecCache = new();
 
-            public Parser(Compilation compilation)
+            private static DiagnosticDescriptor ContextClassesMustBePartial { get; } = new DiagnosticDescriptor(
+                id: "SYSLIB1032",
+                title: new LocalizableResourceString(nameof(SR.ContextClassesMustBePartialTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
+                messageFormat: new LocalizableResourceString(nameof(SR.ContextClassesMustBePartialMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
+                category: SystemTextJsonSourceGenerationName,
+                defaultSeverity: DiagnosticSeverity.Warning,
+                isEnabledByDefault: true);
+
+            public Parser(in GeneratorExecutionContext executionContext)
             {
-                _compilation = compilation;
-                _metadataLoadContext = new MetadataLoadContextInternal(compilation);
+                _executionContext = executionContext;
+                _metadataLoadContext = new MetadataLoadContextInternal(executionContext.Compilation);
 
                 _ienumerableType = _metadataLoadContext.Resolve(typeof(IEnumerable));
                 _listOfTType = _metadataLoadContext.Resolve(typeof(List<>));
@@ -74,6 +85,7 @@ namespace System.Text.Json.SourceGeneration
                 _dateTimeType = _metadataLoadContext.Resolve(typeof(DateTime));
                 _dateTimeOffsetType = _metadataLoadContext.Resolve(typeof(DateTimeOffset));
                 _guidType = _metadataLoadContext.Resolve(typeof(Guid));
+                _nullableOfTType = _metadataLoadContext.Resolve(typeof(Nullable<>));
                 _stringType = _metadataLoadContext.Resolve(typeof(string));
                 _uriType = _metadataLoadContext.Resolve(typeof(Uri));
                 _versionType = _metadataLoadContext.Resolve(typeof(Version));
@@ -81,109 +93,330 @@ namespace System.Text.Json.SourceGeneration
                 PopulateKnownTypes();
             }
 
-            public Dictionary<string, TypeMetadata>? GetRootSerializableTypes(List<CompilationUnitSyntax> compilationUnits)
+            public SourceGenerationSpec? GetGenerationSpec(List<ClassDeclarationSyntax> classDeclarationSyntaxList)
             {
-                TypeExtensions.NullableOfTType = _metadataLoadContext.Resolve(typeof(Nullable<>));
+                Compilation compilation = _executionContext.Compilation;
+                INamedTypeSymbol jsonSerializerContextSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializerContext");
+                INamedTypeSymbol jsonSerializableAttributeSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializableAttribute");
+                INamedTypeSymbol jsonSerializerOptionsAttributeSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializerOptionsAttribute");
 
-                const string JsonSerializableAttributeName = "System.Text.Json.Serialization.JsonSerializableAttribute";
-                INamedTypeSymbol jsonSerializableAttribute = _compilation.GetTypeByMetadataName(JsonSerializableAttributeName);
-                if (jsonSerializableAttribute == null)
+                if (jsonSerializerContextSymbol == null || jsonSerializableAttributeSymbol == null || jsonSerializerOptionsAttributeSymbol == null)
                 {
                     return null;
                 }
 
-                // Discover serializable types indicated by JsonSerializableAttribute.
-                Dictionary<string, TypeMetadata>? rootTypes = null;
+                List<ContextGenerationSpec>? contextGenSpecList = null;
 
-                foreach (CompilationUnitSyntax compilationUnit in compilationUnits)
+                foreach (ClassDeclarationSyntax classDeclarationSyntax in classDeclarationSyntaxList)
                 {
-                    SemanticModel compilationSemanticModel = _compilation.GetSemanticModel(compilationUnit.SyntaxTree);
+                    CompilationUnitSyntax compilationUnitSyntax = classDeclarationSyntax.FirstAncestorOrSelf<CompilationUnitSyntax>();
+                    SemanticModel compilationSemanticModel = compilation.GetSemanticModel(compilationUnitSyntax.SyntaxTree);
+
+                    if (!DerivesFromJsonSerializerContext(classDeclarationSyntax, jsonSerializerContextSymbol, compilationSemanticModel))
+                    {
+                        continue;
+                    }
+
+                    List<TypeGenerationSpec>? rootTypes = null;
+                    JsonSerializerOptionsAttribute? options = null;
 
-                    foreach (AttributeListSyntax attributeListSyntax in compilationUnit.AttributeLists)
+                    foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntax.AttributeLists)
                     {
                         AttributeSyntax attributeSyntax = attributeListSyntax.Attributes.First();
                         IMethodSymbol attributeSymbol = compilationSemanticModel.GetSymbolInfo(attributeSyntax).Symbol as IMethodSymbol;
-
-                        if (attributeSymbol == null || !jsonSerializableAttribute.Equals(attributeSymbol.ContainingType, SymbolEqualityComparer.Default))
+                        if (attributeSymbol == null)
                         {
-                            // Not the right attribute.
                             continue;
                         }
 
-                        // Get JsonSerializableAttribute arguments.
-                        IEnumerable<SyntaxNode> attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax);
-
-                        ITypeSymbol? typeSymbol = null;
-                        string? typeInfoPropertyName = null;
+                        INamedTypeSymbol attributeContainingTypeSymbol = attributeSymbol.ContainingType;
 
-                        int i = 0;
-                        foreach (AttributeArgumentSyntax node in attributeArguments)
+                        if (jsonSerializableAttributeSymbol.Equals(attributeContainingTypeSymbol, SymbolEqualityComparer.Default))
                         {
-                            if (i == 0)
+                            TypeGenerationSpec? metadata = GetRootSerializableType(compilationSemanticModel, attributeSyntax);
+                            if (metadata != null)
                             {
-                                TypeOfExpressionSyntax? typeNode = node.ChildNodes().Single() as TypeOfExpressionSyntax;
-                                if (typeNode != null)
-                                {
-                                    ExpressionSyntax typeNameSyntax = (ExpressionSyntax)typeNode.ChildNodes().Single();
-                                    typeSymbol = compilationSemanticModel.GetTypeInfo(typeNameSyntax).ConvertedType;
-                                }
+                                (rootTypes ??= new List<TypeGenerationSpec>()).Add(metadata);
                             }
-                            else if (i == 1)
+                        }
+                        else if (jsonSerializerOptionsAttributeSymbol.Equals(attributeContainingTypeSymbol, SymbolEqualityComparer.Default))
+                        {
+                            options = GetSerializerOptions(attributeSyntax);
+                        }
+                    }
+
+                    if (rootTypes == null)
+                    {
+                        // No types were indicated with [JsonSerializable]
+                        continue;
+                    }
+
+                    INamedTypeSymbol contextTypeSymbol = (INamedTypeSymbol)compilationSemanticModel.GetDeclaredSymbol(classDeclarationSyntax);
+                    Debug.Assert(contextTypeSymbol != null);
+
+                    if (!TryGetClassDeclarationList(contextTypeSymbol, out List<string> classDeclarationList))
+                    {
+                        // Class or one of its containing types is not partial so we can't add to it.
+                        _executionContext.ReportDiagnostic(Diagnostic.Create(ContextClassesMustBePartial, Location.None, new string[] { contextTypeSymbol.Name }));
+                        continue;
+                    }
+
+                    contextGenSpecList ??= new List<ContextGenerationSpec>();
+                    contextGenSpecList.Add(new ContextGenerationSpec
+                    {
+                        SerializerOptions = options ?? new JsonSerializerOptionsAttribute(),
+                        ContextType = contextTypeSymbol.AsType(_metadataLoadContext),
+                        RootSerializableTypes = rootTypes,
+                        ContextClassDeclarationList = classDeclarationList
+                    });
+
+                    // Clear the cache of generated metadata between the processing of context classes.
+                    _typeGenerationSpecCache.Clear();
+                }
+
+                if (contextGenSpecList == null)
+                {
+                    return null;
+                }
+
+                return new SourceGenerationSpec
+                {
+                    ContextGenerationSpecList = contextGenSpecList,
+                    BooleanType = _booleanType,
+                    ByteArrayType = _byteArrayType,
+                    CharType = _charType,
+                    DateTimeType = _dateTimeType,
+                    DateTimeOffsetType = _dateTimeOffsetType,
+                    GuidType = _guidType,
+                    StringType = _stringType,
+                    NumberTypes = _numberTypes,
+                };
+            }
+
+            // Returns true if a given type derives directly from JsonSerializerContext.
+            private bool DerivesFromJsonSerializerContext(
+                ClassDeclarationSyntax classDeclarationSyntax,
+                INamedTypeSymbol jsonSerializerContextSymbol,
+                SemanticModel compilationSemanticModel)
+            {
+                SeparatedSyntaxList<BaseTypeSyntax>? baseTypeSyntaxList = classDeclarationSyntax.BaseList?.Types;
+                if (baseTypeSyntaxList == null)
+                {
+                    return false;
+                }
+
+                INamedTypeSymbol? match = null;
+
+                foreach (BaseTypeSyntax baseTypeSyntax in baseTypeSyntaxList)
+                {
+                    INamedTypeSymbol? candidate = compilationSemanticModel.GetSymbolInfo(baseTypeSyntax.Type).Symbol as INamedTypeSymbol;
+                    if (candidate != null && jsonSerializerContextSymbol.Equals(candidate, SymbolEqualityComparer.Default))
+                    {
+                        match = candidate;
+                        break;
+                    }
+                }
+
+                return match != null;
+            }
+
+            private static bool TryGetClassDeclarationList(INamedTypeSymbol typeSymbol, [NotNullWhenAttribute(true)] out List<string> classDeclarationList)
+            {
+                classDeclarationList = new();
+
+                INamedTypeSymbol currentSymbol = typeSymbol;
+
+                while (currentSymbol != null)
+                {
+                    ClassDeclarationSyntax? classDeclarationSyntax = currentSymbol.DeclaringSyntaxReferences.First().GetSyntax() as ClassDeclarationSyntax;
+
+                    if (classDeclarationSyntax != null)
+                    {
+                        SyntaxTokenList tokenList = classDeclarationSyntax.Modifiers;
+                        int tokenCount = tokenList.Count;
+
+                        bool isPartial = false;
+
+                        string[] declarationElements = new string[tokenCount + 2];
+
+                        for (int i = 0; i < tokenCount; i++)
+                        {
+                            SyntaxToken token = tokenList[i];
+                            declarationElements[i] = token.Text;
+
+                            if (token.IsKind(SyntaxKind.PartialKeyword))
                             {
-                                // Obtain the optional TypeInfoPropertyName string property on the attribute, if present.
-                                SyntaxNode? typeInfoPropertyNameNode = node.ChildNodes().ElementAtOrDefault(1);
-                                if (typeInfoPropertyNameNode != null)
-                                {
-                                    typeInfoPropertyName = typeInfoPropertyNameNode.GetFirstToken().ValueText;
-                                }
+                                isPartial = true;
                             }
+                        }
 
-                            i++;
+                        if (!isPartial)
+                        {
+                            classDeclarationList = null;
+                            return false;
                         }
 
-                        if (typeSymbol == null)
+                        declarationElements[tokenCount] = "class";
+                        declarationElements[tokenCount + 1] = currentSymbol.Name;
+
+                        classDeclarationList.Add(string.Join(" ", declarationElements));
+                    }
+
+                    currentSymbol = currentSymbol.ContainingType;
+                }
+
+                Debug.Assert(classDeclarationList.Count > 0);
+                return true;
+            }
+
+            private TypeGenerationSpec? GetRootSerializableType(SemanticModel compilationSemanticModel, AttributeSyntax attributeSyntax)
+            {
+                IEnumerable<SyntaxNode> attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax);
+
+                ITypeSymbol? typeSymbol = null;
+                string? typeInfoPropertyName = null;
+                JsonSourceGenerationMode generationMode = default;
+
+                bool seenFirstArg = false;
+                foreach (AttributeArgumentSyntax node in attributeArguments)
+                {
+                    if (!seenFirstArg)
+                    {
+                        TypeOfExpressionSyntax? typeNode = node.ChildNodes().Single() as TypeOfExpressionSyntax;
+                        if (typeNode != null)
                         {
-                            continue;
+                            ExpressionSyntax typeNameSyntax = (ExpressionSyntax)typeNode.ChildNodes().Single();
+                            typeSymbol = compilationSemanticModel.GetTypeInfo(typeNameSyntax).ConvertedType;
                         }
 
+                        seenFirstArg = true;
+                    }
+                    else
+                    {
+                        IEnumerable<SyntaxNode> childNodes = node.ChildNodes();
 
-                        Type type = new TypeWrapper(typeSymbol, _metadataLoadContext);
-                        if (type.Namespace == "<global namespace>")
+                        NameEqualsSyntax? propertyNameNode = childNodes.First() as NameEqualsSyntax;
+                        Debug.Assert(propertyNameNode != null);
+
+                        SyntaxNode? propertyValueMode = childNodes.ElementAtOrDefault(1);
+                        if (propertyNameNode.Name.Identifier.ValueText == "TypeInfoPropertyName")
+                        {
+                            typeInfoPropertyName = propertyValueMode.GetFirstToken().ValueText;
+                        }
+                        else
                         {
-                            // typeof() reference where the type's name isn't fully qualified.
-                            // The compilation is not valid and the user needs to fix their code.
-                            // The compiler will notify the user so we don't have to.
-                            return null;
+                            Debug.Assert(propertyNameNode.Name.Identifier.ValueText == "GenerationMode");
+                            generationMode = (JsonSourceGenerationMode)Enum.Parse(typeof(JsonSourceGenerationMode), propertyValueMode.GetLastToken().ValueText);
                         }
+                    }
+                }
 
-                        rootTypes ??= new Dictionary<string, TypeMetadata>();
-                        rootTypes[type.FullName] = GetOrAddTypeMetadata(type, typeInfoPropertyName);
+                if (typeSymbol == null)
+                {
+                    return null;
+                }
+
+                Type type = typeSymbol.AsType(_metadataLoadContext);
+                if (type.Namespace == "<global namespace>")
+                {
+                    // typeof() reference where the type's name isn't fully qualified.
+                    // The compilation is not valid and the user needs to fix their code.
+                    // The compiler will notify the user so we don't have to.
+                    return null;
+                }
+
+                TypeGenerationSpec typeGenerationSpec = GetOrAddTypeGenerationSpec(type);
+
+                if (typeInfoPropertyName != null)
+                {
+                    typeGenerationSpec.TypeInfoPropertyName = typeInfoPropertyName;
+                }
+
+                ClassType classType = typeGenerationSpec.ClassType;
+                CollectionType collectionType = typeGenerationSpec.CollectionType;
+                switch (generationMode)
+                {
+                    case JsonSourceGenerationMode.MetadataAndSerialization:
+                        break;
+                    case JsonSourceGenerationMode.Metadata:
+                        typeGenerationSpec.GenerateSerializationLogic = false;
+                        break;
+                    case JsonSourceGenerationMode.Serialization:
+                        typeGenerationSpec.GenerateMetadata = false;
+                        break;
+                    default:
+                        throw new InvalidOperationException();
+                }
+
+                return typeGenerationSpec;
+            }
+
+            private static JsonSerializerOptionsAttribute? GetSerializerOptions(AttributeSyntax attributeSyntax)
+            {
+                IEnumerable<SyntaxNode> attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax);
+
+                JsonSerializerOptionsAttribute options = new();
+
+                foreach (AttributeArgumentSyntax node in attributeArguments)
+                {
+                    IEnumerable<SyntaxNode> childNodes = node.ChildNodes();
+
+                    NameEqualsSyntax? propertyNameNode = childNodes.First() as NameEqualsSyntax;
+                    Debug.Assert(propertyNameNode != null);
+
+                    SyntaxNode? propertyValueNode = childNodes.ElementAtOrDefault(1);
+                    string propertyValueStr = propertyValueNode.GetLastToken().ValueText;
+
+                    switch (propertyNameNode.Name.Identifier.ValueText)
+                    {
+                        case "DefaultIgnoreCondition":
+                            options.DefaultIgnoreCondition = (JsonIgnoreCondition)Enum.Parse(typeof(JsonIgnoreCondition), propertyValueStr);
+                            break;
+                        case "IgnoreReadOnlyFields":
+                            options.IgnoreReadOnlyFields = bool.Parse(propertyValueStr);
+                            break;
+                        case "IgnoreReadOnlyProperties":
+                            options.IgnoreReadOnlyProperties = bool.Parse(propertyValueStr);
+                            break;
+                        case "IgnoreRuntimeCustomConverters":
+                            options.IgnoreRuntimeCustomConverters = bool.Parse(propertyValueStr);
+                            break;
+                        case "IncludeFields":
+                            options.IncludeFields = bool.Parse(propertyValueStr);
+                            break;
+                        case "NamingPolicy":
+                            options.NamingPolicy = (JsonKnownNamingPolicy)Enum.Parse(typeof(JsonKnownNamingPolicy), propertyValueStr);
+                            break;
+                        case "WriteIndented":
+                            options.WriteIndented = bool.Parse(propertyValueStr);
+                            break;
+                        default:
+                            throw new InvalidOperationException();
                     }
                 }
 
-                return rootTypes;
+                return options;
             }
 
-            private TypeMetadata GetOrAddTypeMetadata(Type type, string? typeInfoPropertyName = null)
+            private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type)
             {
-                if (_typeMetadataCache.TryGetValue(type, out TypeMetadata? typeMetadata))
+                if (_typeGenerationSpecCache.TryGetValue(type, out TypeGenerationSpec? typeMetadata))
                 {
                     return typeMetadata!;
                 }
 
                 // Add metadata to cache now to prevent stack overflow when the same type is found somewhere else in the object graph.
                 typeMetadata = new();
-                _typeMetadataCache[type] = typeMetadata;
+                _typeGenerationSpecCache[type] = typeMetadata;
 
                 ClassType classType;
                 Type? collectionKeyType = null;
                 Type? collectionValueType = null;
                 Type? nullableUnderlyingType = null;
-                List<PropertyMetadata>? propertiesMetadata = null;
+                List<PropertyGenerationSpec>? propertiesMetadata = null;
                 CollectionType collectionType = CollectionType.NotApplicable;
                 ObjectConstructionStrategy constructionStrategy = default;
                 JsonNumberHandling? numberHandling = null;
-                bool containsOnlyPrimitives = true;
 
                 bool foundDesignTimeCustomConverter = false;
                 string? converterInstatiationLogic = null;
@@ -215,7 +448,7 @@ namespace System.Text.Json.SourceGeneration
                 {
                     classType = ClassType.KnownType;
                 }
-                else if (type.IsNullableValueType(out nullableUnderlyingType))
+                else if (type.IsNullableValueType(_nullableOfTType, out nullableUnderlyingType))
                 {
                     Debug.Assert(nullableUnderlyingType != null);
                     classType = ClassType.Nullable;
@@ -281,7 +514,7 @@ namespace System.Text.Json.SourceGeneration
 
                         foreach (PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags))
                         {
-                            PropertyMetadata metadata = GetPropertyMetadata(propertyInfo);
+                            PropertyGenerationSpec metadata = GetPropertyGenerationSpec(propertyInfo);
 
                             // Ignore indexers.
                             if (propertyInfo.GetIndexParameters().Length > 0)
@@ -289,24 +522,17 @@ namespace System.Text.Json.SourceGeneration
                                 continue;
                             }
 
-                            string key = metadata.JsonPropertyName ?? metadata.ClrName;
-
-                            if (metadata.HasGetter || metadata.HasSetter)
+                            if (metadata.CanUseGetter || metadata.CanUseSetter)
                             {
                                 (propertiesMetadata ??= new()).Add(metadata);
                             }
-
-                            if (containsOnlyPrimitives && !IsPrimitive(propertyInfo.PropertyType))
-                            {
-                                containsOnlyPrimitives = false;
-                            }
                         }
 
                         foreach (FieldInfo fieldInfo in currentType.GetFields(bindingFlags))
                         {
-                            PropertyMetadata metadata = GetPropertyMetadata(fieldInfo);
+                            PropertyGenerationSpec metadata = GetPropertyGenerationSpec(fieldInfo);
 
-                            if (metadata.HasGetter || metadata.HasSetter)
+                            if (metadata.CanUseGetter || metadata.CanUseSetter)
                             {
                                 (propertiesMetadata ??= new()).Add(metadata);
                             }
@@ -315,25 +541,24 @@ namespace System.Text.Json.SourceGeneration
                 }
 
                 typeMetadata.Initialize(
-                    compilableName: type.GetUniqueCompilableTypeName(),
-                    friendlyName: typeInfoPropertyName ?? type.GetFriendlyTypeName(),
+                    typeRef: type.GetUniqueCompilableTypeName(),
+                    typeInfoPropertyName: type.GetFriendlyTypeName(),
                     type,
                     classType,
                     isValueType: type.IsValueType,
                     numberHandling,
                     propertiesMetadata,
                     collectionType,
-                    collectionKeyTypeMetadata: collectionKeyType != null ? GetOrAddTypeMetadata(collectionKeyType) : null,
-                    collectionValueTypeMetadata: collectionValueType != null ? GetOrAddTypeMetadata(collectionValueType) : null,
+                    collectionKeyTypeMetadata: collectionKeyType != null ? GetOrAddTypeGenerationSpec(collectionKeyType) : null,
+                    collectionValueTypeMetadata: collectionValueType != null ? GetOrAddTypeGenerationSpec(collectionValueType) : null,
                     constructionStrategy,
-                    nullableUnderlyingTypeMetadata: nullableUnderlyingType != null ? GetOrAddTypeMetadata(nullableUnderlyingType) : null,
-                    converterInstatiationLogic,
-                    containsOnlyPrimitives);
+                    nullableUnderlyingTypeMetadata: nullableUnderlyingType != null ? GetOrAddTypeGenerationSpec(nullableUnderlyingType) : null,
+                    converterInstatiationLogic);
 
                 return typeMetadata;
             }
 
-            private PropertyMetadata GetPropertyMetadata(MemberInfo memberInfo)
+            private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo)
             {
                 IList<CustomAttributeData> attributeDataList = CustomAttributeData.GetCustomAttributes(memberInfo);
 
@@ -399,8 +624,9 @@ namespace System.Text.Json.SourceGeneration
                 }
 
                 Type memberCLRType;
-                bool hasGetter;
-                bool hasSetter;
+                bool isReadOnly;
+                bool canUseGetter;
+                bool canUseSetter;
                 bool getterIsVirtual = false;
                 bool setterIsVirtual = false;
 
@@ -409,10 +635,10 @@ namespace System.Text.Json.SourceGeneration
                     case PropertyInfo propertyInfo:
                         {
                             MethodInfo setMethod = propertyInfo.SetMethod;
-
                             memberCLRType = propertyInfo.PropertyType;
-                            hasGetter = PropertyAccessorCanBeReferenced(propertyInfo.GetMethod, hasJsonInclude);
-                            hasSetter = PropertyAccessorCanBeReferenced(setMethod, hasJsonInclude) && !setMethod.IsInitOnly();
+                            isReadOnly = setMethod == null;
+                            canUseGetter = PropertyAccessorCanBeReferenced(propertyInfo.GetMethod, hasJsonInclude);
+                            canUseSetter = PropertyAccessorCanBeReferenced(setMethod, hasJsonInclude) && !setMethod.IsInitOnly();
                             getterIsVirtual = propertyInfo.GetMethod?.IsVirtual == true;
                             setterIsVirtual = propertyInfo.SetMethod?.IsVirtual == true;
                         }
@@ -420,30 +646,31 @@ namespace System.Text.Json.SourceGeneration
                     case FieldInfo fieldInfo:
                         {
                             Debug.Assert(fieldInfo.IsPublic);
-
                             memberCLRType = fieldInfo.FieldType;
-                            hasGetter = true;
-                            hasSetter = !fieldInfo.IsInitOnly;
+                            isReadOnly = fieldInfo.IsInitOnly;
+                            canUseGetter = true;
+                            canUseSetter = !isReadOnly;
                         }
                         break;
                     default:
                         throw new InvalidOperationException();
                 }
 
-                return new PropertyMetadata
+                return new PropertyGenerationSpec
                 {
                     ClrName = memberInfo.Name,
                     IsProperty = memberInfo.MemberType == MemberTypes.Property,
                     JsonPropertyName = jsonPropertyName,
-                    HasGetter = hasGetter,
-                    HasSetter = hasSetter,
+                    IsReadOnly = isReadOnly,
+                    CanUseGetter = canUseGetter,
+                    CanUseSetter = canUseSetter,
                     GetterIsVirtual = getterIsVirtual,
                     SetterIsVirtual = setterIsVirtual,
-                    IgnoreCondition = ignoreCondition,
+                    DefaultIgnoreCondition = ignoreCondition,
                     NumberHandling = numberHandling,
                     HasJsonInclude = hasJsonInclude,
-                    TypeMetadata = GetOrAddTypeMetadata(memberCLRType),
-                    DeclaringTypeCompilableName = memberInfo.DeclaringType.GetUniqueCompilableTypeName(),
+                    TypeGenerationSpec = GetOrAddTypeGenerationSpec(memberCLRType),
+                    DeclaringTypeRef = $"global::{memberInfo.DeclaringType.GetUniqueCompilableTypeName()}",
                     ConverterInstantiationLogic = converterInstantiationLogic
                 };
             }
@@ -458,7 +685,8 @@ namespace System.Text.Json.SourceGeneration
                     return null;
                 }
 
-                Type converterType = new TypeWrapper((ITypeSymbol)attributeData.ConstructorArguments[0].Value, _metadataLoadContext);
+                ITypeSymbol converterTypeSymbol = (ITypeSymbol)attributeData.ConstructorArguments[0].Value;
+                Type converterType = converterTypeSymbol.AsType(_metadataLoadContext);
 
                 if (converterType == null || converterType.GetConstructor(Type.EmptyTypes) == null || converterType.IsNestedPrivate)
                 {
@@ -509,9 +737,6 @@ namespace System.Text.Json.SourceGeneration
 
                 _knownTypes.Add(_metadataLoadContext.Resolve(typeof(Version)));
             }
-
-            private bool IsPrimitive(Type type)
-                => _knownTypes.Contains(type) && type != _uriType && type != _versionType;
         }
     }
 }
index eb23225..d49dfd0 100644 (file)
@@ -19,12 +19,6 @@ namespace System.Text.Json.SourceGeneration
     public sealed partial class JsonSourceGenerator : ISourceGenerator
     {
         /// <summary>
-        /// Helper for unit tests.
-        /// </summary>
-        public Dictionary<string, Type>? GetSerializableTypes() => _rootTypes?.ToDictionary(p => p.Key, p => p.Value.Type);
-        private Dictionary<string, TypeMetadata>? _rootTypes;
-
-        /// <summary>
         /// Registers a syntax resolver to receive compilation units.
         /// </summary>
         /// <param name="context"></param>
@@ -39,32 +33,42 @@ namespace System.Text.Json.SourceGeneration
         /// <param name="executionContext"></param>
         public void Execute(GeneratorExecutionContext executionContext)
         {
+            //if (!Diagnostics.Debugger.IsAttached) { Diagnostics.Debugger.Launch(); };
             SyntaxReceiver receiver = (SyntaxReceiver)executionContext.SyntaxReceiver;
-            List<CompilationUnitSyntax> compilationUnits = receiver.CompilationUnits;
-            if (compilationUnits == null)
+            List<ClassDeclarationSyntax>? contextClasses = receiver.ClassDeclarationSyntaxList;
+            if (contextClasses == null)
             {
                 return;
             }
 
-            Parser parser = new(executionContext.Compilation);
-            _rootTypes = parser.GetRootSerializableTypes(receiver.CompilationUnits);
-
-            if (_rootTypes != null)
+            Parser parser = new(executionContext);
+            SourceGenerationSpec? spec = parser.GetGenerationSpec(receiver.ClassDeclarationSyntaxList);
+            if (spec != null)
             {
-                Emitter emitter = new(executionContext, _rootTypes);
+                _rootTypes = spec.ContextGenerationSpecList[0].RootSerializableTypes;
+
+                Emitter emitter = new(executionContext, spec);
                 emitter.Emit();
             }
         }
 
-        internal sealed class SyntaxReceiver : ISyntaxReceiver
+        private const string SystemTextJsonSourceGenerationName = "System.Text.Json.SourceGeneration";
+
+        /// <summary>
+        /// Helper for unit tests.
+        /// </summary>
+        public Dictionary<string, Type>? GetSerializableTypes() => _rootTypes?.ToDictionary(p => p.Type.FullName, p => p.Type);
+        private List<TypeGenerationSpec>? _rootTypes;
+
+        private sealed class SyntaxReceiver : ISyntaxReceiver
         {
-            public List<CompilationUnitSyntax>? CompilationUnits { get; private set; }
+            public List<ClassDeclarationSyntax>? ClassDeclarationSyntaxList { get; private set; }
 
             public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
             {
-                if (syntaxNode is CompilationUnitSyntax compilationUnit)
+                if (syntaxNode is ClassDeclarationSyntax cds)
                 {
-                    (CompilationUnits ??= new List<CompilationUnitSyntax>()).Add(compilationUnit);
+                    (ClassDeclarationSyntaxList ??= new List<ClassDeclarationSyntax>()).Add(cds);
                 }
             }
         }
@@ -7,7 +7,7 @@ using System.Text.Json.Serialization;
 namespace System.Text.Json.SourceGeneration
 {
     [DebuggerDisplay("Name={Name}, Type={TypeMetadata}")]
-    internal class PropertyMetadata
+    internal sealed class PropertyGenerationSpec
     {
         /// <summary>
         /// The CLR name of the property.
@@ -25,16 +25,21 @@ namespace System.Text.Json.SourceGeneration
         public string? JsonPropertyName { get; init; }
 
         /// <summary>
+        /// Whether the property has a set method.
+        /// </summary>
+        public bool IsReadOnly { get; init; }
+
+        /// <summary>
         /// Whether the property has a public or internal (only usable when JsonIncludeAttribute is specified)
         /// getter that can be referenced in generated source code.
         /// </summary>
-        public bool HasGetter { get; init; }
+        public bool CanUseGetter { get; init; }
 
         /// <summary>
         /// Whether the property has a public or internal (only usable when JsonIncludeAttribute is specified)
         /// setter that can be referenced in generated source code.
         /// </summary>
-        public bool HasSetter { get; init; }
+        public bool CanUseSetter { get; init; }
 
         public bool GetterIsVirtual { get; init; }
 
@@ -43,7 +48,7 @@ namespace System.Text.Json.SourceGeneration
         /// <summary>
         /// The <see cref="JsonIgnoreCondition"/> for the property.
         /// </summary>
-        public JsonIgnoreCondition? IgnoreCondition { get; init; }
+        public JsonIgnoreCondition? DefaultIgnoreCondition { get; init; }
 
         /// <summary>
         /// The <see cref="JsonNumberHandling"/> for the property.
@@ -56,14 +61,14 @@ namespace System.Text.Json.SourceGeneration
         public bool HasJsonInclude { get; init; }
 
         /// <summary>
-        /// Metadata for the property's type.
+        /// Generation specification for the property's type.
         /// </summary>
-        public TypeMetadata TypeMetadata { get; init; }
+        public TypeGenerationSpec TypeGenerationSpec { get; init; }
 
         /// <summary>
         /// Compilable name of the property's declaring type.
         /// </summary>
-        public string DeclaringTypeCompilableName { get; init; }
+        public string DeclaringTypeRef { get; init; }
 
         /// <summary>
         /// Source code to instantiate design-time specified custom converter.
index 4bfb71a..8ff487a 100644 (file)
@@ -38,16 +38,14 @@ namespace System.Text.Json.SourceGeneration.Reflection
             return compilableName.Replace(".", "").Replace("<", "").Replace(">", "").Replace(",", "").Replace("[]", "Array");
         }
 
-        public static Type NullableOfTType { get; set; }
-
-        public static bool IsNullableValueType(this Type type, out Type? underlyingType)
+        public static bool IsNullableValueType(this Type type, Type nullableOfTType, out Type? underlyingType)
         {
-            Debug.Assert(NullableOfTType != null);
+            Debug.Assert(nullableOfTType != null);
 
             // TODO: log bug because Nullable.GetUnderlyingType doesn't work due to
             // https://github.com/dotnet/runtimelab/blob/7472c863db6ec5ddab7f411ddb134a6e9f3c105f/src/libraries/System.Private.CoreLib/src/System/Nullable.cs#L124
             // i.e. type.GetGenericTypeDefinition() will never equal typeof(Nullable<>), as expected in that code segment.
-            if (type.IsGenericType && type.GetGenericTypeDefinition() == NullableOfTType)
+            if (type.IsGenericType && type.GetGenericTypeDefinition() == nullableOfTType)
             {
                 underlyingType = type.GetGenericArguments()[0];
                 return true;
@@ -57,6 +55,22 @@ namespace System.Text.Json.SourceGeneration.Reflection
             return false;
         }
 
+        public static bool IsNullableValueType(this Type type, out Type? underlyingType)
+        {
+            if (type.IsGenericType && type.Name.StartsWith("Nullable`1"))
+            {
+                underlyingType = type.GetGenericArguments()[0];
+                return true;
+            }
+
+            underlyingType = null;
+            return false;
+        }
+
+        public static bool IsObjectType(this Type type) => type.FullName == "System.Object";
+
+        public static bool IsStringType(this Type type) => type.FullName == "System.String";
+
         public static Type? GetCompatibleBaseClass(this Type type, string baseTypeFullName)
         {
             Type? baseTypeToCheck = type;
index 4560355..d6d4a15 100644 (file)
   <data name="TypeNotSupportedTitle" xml:space="preserve">
     <value>Did not generate serialization metadata for type.</value>
   </data>
+  <data name="ContextClassesMustBePartialMessageFormat" xml:space="preserve">
+    <value>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</value>
+  </data>
+  <data name="ContextClassesMustBePartialTitle" xml:space="preserve">
+    <value>Derived 'JsonSerializerContext' types and all containing types must be partial.</value>
+  </data>
 </root>
\ No newline at end of file
index 85182d5..d27ae15 100644 (file)
@@ -2,6 +2,16 @@
 <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
   <file datatype="xml" source-language="en" target-language="cs" original="../Strings.resx">
     <body>
+      <trans-unit id="ContextClassesMustBePartialMessageFormat">
+        <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+        <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ContextClassesMustBePartialTitle">
+        <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+        <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
index 1b94670..d2e73c0 100644 (file)
@@ -2,6 +2,16 @@
 <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
   <file datatype="xml" source-language="en" target-language="de" original="../Strings.resx">
     <body>
+      <trans-unit id="ContextClassesMustBePartialMessageFormat">
+        <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+        <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ContextClassesMustBePartialTitle">
+        <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+        <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
index 55915ce..176e213 100644 (file)
@@ -2,6 +2,16 @@
 <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
   <file datatype="xml" source-language="en" target-language="es" original="../Strings.resx">
     <body>
+      <trans-unit id="ContextClassesMustBePartialMessageFormat">
+        <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+        <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ContextClassesMustBePartialTitle">
+        <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+        <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
index cb63078..288e44e 100644 (file)
@@ -2,6 +2,16 @@
 <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
   <file datatype="xml" source-language="en" target-language="fr" original="../Strings.resx">
     <body>
+      <trans-unit id="ContextClassesMustBePartialMessageFormat">
+        <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+        <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ContextClassesMustBePartialTitle">
+        <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+        <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
index 2ac4e6f..434a86f 100644 (file)
@@ -2,6 +2,16 @@
 <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
   <file datatype="xml" source-language="en" target-language="it" original="../Strings.resx">
     <body>
+      <trans-unit id="ContextClassesMustBePartialMessageFormat">
+        <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+        <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ContextClassesMustBePartialTitle">
+        <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+        <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
index 4661920..1ed4855 100644 (file)
@@ -2,6 +2,16 @@
 <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
   <file datatype="xml" source-language="en" target-language="ja" original="../Strings.resx">
     <body>
+      <trans-unit id="ContextClassesMustBePartialMessageFormat">
+        <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+        <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ContextClassesMustBePartialTitle">
+        <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+        <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
index 322cc79..e503e6b 100644 (file)
@@ -2,6 +2,16 @@
 <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
   <file datatype="xml" source-language="en" target-language="ko" original="../Strings.resx">
     <body>
+      <trans-unit id="ContextClassesMustBePartialMessageFormat">
+        <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+        <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ContextClassesMustBePartialTitle">
+        <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+        <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
index 408e27c..728d28a 100644 (file)
@@ -2,6 +2,16 @@
 <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
   <file datatype="xml" source-language="en" target-language="pl" original="../Strings.resx">
     <body>
+      <trans-unit id="ContextClassesMustBePartialMessageFormat">
+        <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+        <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ContextClassesMustBePartialTitle">
+        <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+        <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
index e131d25..b02aae1 100644 (file)
@@ -2,6 +2,16 @@
 <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
   <file datatype="xml" source-language="en" target-language="pt-BR" original="../Strings.resx">
     <body>
+      <trans-unit id="ContextClassesMustBePartialMessageFormat">
+        <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+        <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ContextClassesMustBePartialTitle">
+        <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+        <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
index f02ca1c..ebfd3c9 100644 (file)
@@ -2,6 +2,16 @@
 <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
   <file datatype="xml" source-language="en" target-language="ru" original="../Strings.resx">
     <body>
+      <trans-unit id="ContextClassesMustBePartialMessageFormat">
+        <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+        <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ContextClassesMustBePartialTitle">
+        <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+        <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
index 2a00a92..0e59048 100644 (file)
@@ -2,6 +2,16 @@
 <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
   <file datatype="xml" source-language="en" target-language="tr" original="../Strings.resx">
     <body>
+      <trans-unit id="ContextClassesMustBePartialMessageFormat">
+        <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+        <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ContextClassesMustBePartialTitle">
+        <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+        <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
index 2822193..27a56b7 100644 (file)
@@ -2,6 +2,16 @@
 <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
   <file datatype="xml" source-language="en" target-language="zh-Hans" original="../Strings.resx">
     <body>
+      <trans-unit id="ContextClassesMustBePartialMessageFormat">
+        <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+        <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ContextClassesMustBePartialTitle">
+        <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+        <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
index c5c1cc1..9ba73dc 100644 (file)
@@ -2,6 +2,16 @@
 <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
   <file datatype="xml" source-language="en" target-language="zh-Hant" original="../Strings.resx">
     <body>
+      <trans-unit id="ContextClassesMustBePartialMessageFormat">
+        <source>Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</source>
+        <target state="new">Derived 'JsonSerializerContext' type '{0}' specifies JSON-serializable types. The type and all containing types must be made partial to kick off source generation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ContextClassesMustBePartialTitle">
+        <source>Derived 'JsonSerializerContext' types and all containing types must be partial.</source>
+        <target state="new">Derived 'JsonSerializerContext' types and all containing types must be partial.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="DuplicateTypeNameMessageFormat">
         <source>There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</source>
         <target state="new">There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.</target>
diff --git a/src/libraries/System.Text.Json/gen/SourceGenerationSpec.cs b/src/libraries/System.Text.Json/gen/SourceGenerationSpec.cs
new file mode 100644 (file)
index 0000000..0019dc5
--- /dev/null
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace System.Text.Json.SourceGeneration
+{
+    internal sealed class SourceGenerationSpec
+    {
+        public List<ContextGenerationSpec> ContextGenerationSpecList { get; init; }
+
+        public Type BooleanType { get; init; }
+        public Type ByteArrayType { get; init; }
+        public Type CharType { get; init; }
+        public Type DateTimeType { private get; init; }
+        public Type DateTimeOffsetType { private get; init; }
+        public Type GuidType { private get; init; }
+        public Type StringType { private get; init; }
+
+        public HashSet<Type> NumberTypes { private get; init; }
+
+        public bool IsStringBasedType(Type type)
+            => type == StringType || type == DateTimeType || type == DateTimeOffsetType || type == GuidType;
+
+        public bool IsNumberType(Type type) => NumberTypes.Contains(type);
+    }
+}
index 74dd28a..5ed7281 100644 (file)
 
   <ItemGroup>
     <Compile Include="$(CommonPath)System\Runtime\CompilerServices\IsExternalInit.cs" Link="Common\System\Runtime\CompilerServices\IsExternalInit.cs" />
+    <Compile Include="..\Common\JsonCamelCaseNamingPolicy.cs" Link="Common\System\Text\Json\JsonCamelCaseNamingPolicy.cs" />
+    <Compile Include="..\Common\JsonNamingPolicy.cs" Link="Common\System\Text\Json\JsonNamingPolicy.cs" />
+    <Compile Include="..\Common\JsonAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonAttribute.cs" />
     <Compile Include="..\Common\JsonIgnoreCondition.cs" Link="Common\System\Text\Json\Serialization\JsonIgnoreCondition.cs" />
+    <Compile Include="..\Common\JsonKnownNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonKnownNamingPolicy.cs" />
     <Compile Include="..\Common\JsonNumberHandling.cs" Link="Common\System\Text\Json\Serialization\JsonNumberHandling.cs" />
+    <Compile Include="..\Common\JsonSerializerOptionsAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonSerializerOptionsAttribute.cs" />
+    <Compile Include="..\Common\JsonSourceGenerationMode.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationMode.cs" />
     <Compile Include="ClassType.cs" />
     <Compile Include="CollectionType.cs" />
     <Compile Include="JsonSourceGenerator.cs" />
     <Compile Include="JsonSourceGenerator.Emitter.cs" />
     <Compile Include="JsonSourceGenerator.Parser.cs" />
     <Compile Include="ObjectConstructionStrategy.cs" />
-    <Compile Include="PropertyMetadata.cs" />
+    <Compile Include="PropertyGenerationSpec.cs" />
     <Compile Include="Reflection\AssemblyWrapper.cs" />
     <Compile Include="Reflection\TypeExtensions.cs" />
     <Compile Include="Reflection\FieldInfoWrapper.cs" />
@@ -44,6 +50,8 @@
     <Compile Include="Reflection\ReflectionExtensions.cs" />
     <Compile Include="Reflection\RoslynExtensions.cs" />
     <Compile Include="Reflection\TypeWrapper.cs" />
-    <Compile Include="TypeMetadata.cs" />
+    <Compile Include="ContextGenerationSpec.cs" />
+    <Compile Include="SourceGenerationSpec.cs" />
+    <Compile Include="TypeGenerationSpec.cs" />
   </ItemGroup>
 </Project>
diff --git a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs
new file mode 100644 (file)
index 0000000..20155f0
--- /dev/null
@@ -0,0 +1,110 @@
+// 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.Text.Json.Serialization;
+using System.Text.Json.SourceGeneration.Reflection;
+
+namespace System.Text.Json.SourceGeneration
+{
+    [DebuggerDisplay("Type={Type}, ClassType={ClassType}")]
+    internal class TypeGenerationSpec
+    {
+        /// <summary>
+        /// Fully qualified assembly name, prefixed with "global::", e.g. global::System.Numerics.BigInteger.
+        /// </summary>
+        public string TypeRef { get; private set; }
+
+        /// <summary>
+        /// The name of the public JsonTypeInfo<T> property for this type on the generated context class.
+        /// For example, if the context class is named MyJsonContext, and the value of this property is JsonMessage;
+        /// then users will call MyJsonContext.JsonMessage to access generated metadata for the type.
+        /// </summary>
+        public string TypeInfoPropertyName { get; set; }
+
+        public bool GenerateMetadata { get; set; } = true;
+
+        private bool? _generateSerializationLogic;
+        public bool GenerateSerializationLogic
+        {
+            get => _generateSerializationLogic ??= FastPathIsSupported();
+            set => _generateSerializationLogic = value;
+        }
+
+        public Type Type { get; private set; }
+
+        public ClassType ClassType { get; private set; }
+
+        public bool IsValueType { get; private set; }
+
+        public bool CanBeNull { get; private set; }
+
+        public JsonNumberHandling? NumberHandling { get; private set; }
+
+        public List<PropertyGenerationSpec>? PropertiesMetadata { get; private set; }
+
+        public CollectionType CollectionType { get; private set; }
+
+        public TypeGenerationSpec? CollectionKeyTypeMetadata { get; private set; }
+
+        public TypeGenerationSpec? CollectionValueTypeMetadata { get; private set; }
+
+        public ObjectConstructionStrategy ConstructionStrategy { get; private set; }
+
+        public TypeGenerationSpec? NullableUnderlyingTypeMetadata { get; private set; }
+
+        public string? ConverterInstantiationLogic { get; private set; }
+
+        public void Initialize(
+            string typeRef,
+            string typeInfoPropertyName,
+            Type type,
+            ClassType classType,
+            bool isValueType,
+            JsonNumberHandling? numberHandling,
+            List<PropertyGenerationSpec>? propertiesMetadata,
+            CollectionType collectionType,
+            TypeGenerationSpec? collectionKeyTypeMetadata,
+            TypeGenerationSpec? collectionValueTypeMetadata,
+            ObjectConstructionStrategy constructionStrategy,
+            TypeGenerationSpec? nullableUnderlyingTypeMetadata,
+            string? converterInstantiationLogic)
+        {
+            TypeRef = $"global::{typeRef}";
+            TypeInfoPropertyName = typeInfoPropertyName;
+            Type = type;
+            ClassType = classType;
+            IsValueType = isValueType;
+            CanBeNull = !isValueType || nullableUnderlyingTypeMetadata != null;
+            NumberHandling = numberHandling;
+            PropertiesMetadata = propertiesMetadata;
+            CollectionType = collectionType;
+            CollectionKeyTypeMetadata = collectionKeyTypeMetadata;
+            CollectionValueTypeMetadata = collectionValueTypeMetadata;
+            ConstructionStrategy = constructionStrategy;
+            NullableUnderlyingTypeMetadata = nullableUnderlyingTypeMetadata;
+            ConverterInstantiationLogic = converterInstantiationLogic;
+        }
+
+        public bool FastPathIsSupported()
+        {
+            if (ClassType == ClassType.Object)
+            {
+                return true;
+            }
+
+            if (CollectionType == CollectionType.Array || CollectionType == CollectionType.List)
+            {
+                return !CollectionValueTypeMetadata!.Type.IsObjectType();
+            }
+
+            if (CollectionType == CollectionType.Dictionary)
+            {
+                return CollectionKeyTypeMetadata!.Type.IsStringType() && !CollectionValueTypeMetadata!.Type.IsObjectType();
+            }
+
+            return false;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/gen/TypeMetadata.cs b/src/libraries/System.Text.Json/gen/TypeMetadata.cs
deleted file mode 100644 (file)
index 7f9ea63..0000000
+++ /dev/null
@@ -1,82 +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.Text.Json.Serialization;
-
-namespace System.Text.Json.SourceGeneration
-{
-    [DebuggerDisplay("Type={Type}, ClassType={ClassType}")]
-    internal class TypeMetadata
-    {
-        private bool _hasBeenInitialized;
-
-        public string CompilableName { get; private set; }
-
-        public string FriendlyName { get; private set; }
-
-        public Type Type { get; private set; }
-
-        public ClassType ClassType { get; private set; }
-
-        public bool IsValueType { get; private set; }
-
-        public JsonNumberHandling? NumberHandling { get; private set; }
-
-        public List<PropertyMetadata>? PropertiesMetadata { get; private set; }
-
-        public CollectionType CollectionType { get; private set; }
-
-        public TypeMetadata? CollectionKeyTypeMetadata { get; private set; }
-
-        public TypeMetadata? CollectionValueTypeMetadata { get; private set; }
-
-        public ObjectConstructionStrategy ConstructionStrategy { get; private set; }
-
-        public TypeMetadata? NullableUnderlyingTypeMetadata { get; private set; }
-
-        public string? ConverterInstantiationLogic { get; private set; }
-
-        public bool ContainsOnlyPrimitives { get; private set; }
-
-        public void Initialize(
-            string compilableName,
-            string friendlyName,
-            Type type,
-            ClassType classType,
-            bool isValueType,
-            JsonNumberHandling? numberHandling,
-            List<PropertyMetadata>? propertiesMetadata,
-            CollectionType collectionType,
-            TypeMetadata? collectionKeyTypeMetadata,
-            TypeMetadata? collectionValueTypeMetadata,
-            ObjectConstructionStrategy constructionStrategy,
-            TypeMetadata? nullableUnderlyingTypeMetadata,
-            string? converterInstantiationLogic,
-            bool containsOnlyPrimitives)
-        {
-            if (_hasBeenInitialized)
-            {
-                throw new InvalidOperationException("Type metadata has already been initialized.");
-            }
-
-            _hasBeenInitialized = true;
-
-            CompilableName = compilableName;
-            FriendlyName = friendlyName;
-            Type = type;
-            ClassType = classType;
-            IsValueType = isValueType;
-            NumberHandling = numberHandling;
-            PropertiesMetadata = propertiesMetadata;
-            CollectionType = collectionType;
-            CollectionKeyTypeMetadata = collectionKeyTypeMetadata;
-            CollectionValueTypeMetadata = collectionValueTypeMetadata;
-            ConstructionStrategy = constructionStrategy;
-            NullableUnderlyingTypeMetadata = nullableUnderlyingTypeMetadata;
-            ConverterInstantiationLogic = converterInstantiationLogic;
-            ContainsOnlyPrimitives = containsOnlyPrimitives;
-        }
-    }
-}
index 6664460..efc83f0 100644 (file)
@@ -777,6 +777,11 @@ namespace System.Text.Json.Serialization
     {
         public JsonIncludeAttribute() { }
     }
+    public enum JsonKnownNamingPolicy
+    {
+        Unspecified = 0,
+        BuiltInCamelCase = 1,
+    }
     [System.FlagsAttribute]
     public enum JsonNumberHandling
     {
@@ -797,18 +802,38 @@ namespace System.Text.Json.Serialization
         public JsonPropertyNameAttribute(string name) { }
         public string Name { get { throw null; } }
     }
-    [System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=true)]
+    [System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=true)]
     public sealed partial class JsonSerializableAttribute : System.Text.Json.Serialization.JsonAttribute
     {
         public JsonSerializableAttribute(System.Type type) { }
         public string? TypeInfoPropertyName { get { throw null; } set { } }
+        public System.Text.Json.Serialization.JsonSourceGenerationMode GenerationMode { get { throw null; } set { } }
     }
     public abstract partial class JsonSerializerContext
     {
-        protected JsonSerializerContext(System.Text.Json.JsonSerializerOptions? options) { }
+        protected JsonSerializerContext(System.Text.Json.JsonSerializerOptions? instanceOptions, System.Text.Json.JsonSerializerOptions? defaultOptions) { }
         public System.Text.Json.JsonSerializerOptions Options { get { throw null; } }
         public abstract System.Text.Json.Serialization.Metadata.JsonTypeInfo? GetTypeInfo(System.Type type);
     }
+    [System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=false)]
+    public partial class JsonSerializerOptionsAttribute : System.Text.Json.Serialization.JsonAttribute
+    {
+        public JsonSerializerOptionsAttribute() { }
+        public System.Text.Json.Serialization.JsonIgnoreCondition DefaultIgnoreCondition { get { throw null; } set { } }
+        public bool IgnoreReadOnlyFields { get { throw null; } set { } }
+        public bool IgnoreReadOnlyProperties { get { throw null; } set { } }
+        public bool IgnoreRuntimeCustomConverters { get { throw null; } set { } }
+        public bool IncludeFields { get { throw null; } set { } }
+        public System.Text.Json.Serialization.JsonKnownNamingPolicy NamingPolicy { get { throw null; } set { } }
+        public bool WriteIndented { get { throw null; } set { } }
+    }
+    [System.FlagsAttribute]
+    public enum JsonSourceGenerationMode
+    {
+        MetadataAndSerialization = 0,
+        Metadata = 1,
+        Serialization = 2,
+    }
     public sealed partial class JsonStringEnumConverter : System.Text.Json.Serialization.JsonConverterFactory
     {
         public JsonStringEnumConverter() { }
@@ -870,15 +895,14 @@ namespace System.Text.Json.Serialization.Metadata
         public static System.Text.Json.Serialization.JsonConverter<ulong> UInt64Converter { get { throw null; } }
         public static System.Text.Json.Serialization.JsonConverter<System.Uri> UriConverter { get { throw null; } }
         public static System.Text.Json.Serialization.JsonConverter<System.Version> VersionConverter { get { throw null; } }
-        public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TElement[]> CreateArrayInfo<TElement>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling) { throw null; }
-        public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateDictionaryInfo<TCollection, TKey, TValue>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection> createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo keyInfo, System.Text.Json.Serialization.Metadata.JsonTypeInfo valueInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling) where TCollection : System.Collections.Generic.Dictionary<TKey, TValue> where TKey : notnull { throw null; }
-        public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateListInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection>? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling) where TCollection : System.Collections.Generic.List<TElement> { throw null; }
-        public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> CreateObjectInfo<T>() where T : notnull { throw null; }
+        public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TElement[]> CreateArrayInfo<TElement>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<System.Text.Json.Utf8JsonWriter, TElement[]>? serializeFunc) { throw null; }
+        public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateDictionaryInfo<TCollection, TKey, TValue>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection> createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo keyInfo, System.Text.Json.Serialization.Metadata.JsonTypeInfo valueInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<System.Text.Json.Utf8JsonWriter, TCollection>? serializeFunc) where TCollection : System.Collections.Generic.Dictionary<TKey, TValue> where TKey : notnull { throw null; }
+        public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateListInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection>? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<System.Text.Json.Utf8JsonWriter, TCollection>? serializeFunc) where TCollection : System.Collections.Generic.List<TElement> { throw null; }
+        public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> CreateObjectInfo<T>(System.Text.Json.JsonSerializerOptions options, System.Func<T>? createObjectFunc, System.Func<System.Text.Json.Serialization.JsonSerializerContext, System.Text.Json.Serialization.Metadata.JsonPropertyInfo[]>? propInitFunc, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<System.Text.Json.Utf8JsonWriter, T>? serializeFunc) where T : notnull { throw null; }
         public static System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreatePropertyInfo<T>(System.Text.Json.JsonSerializerOptions options, bool isProperty, System.Type declaringType, System.Text.Json.Serialization.Metadata.JsonTypeInfo propertyTypeInfo, System.Text.Json.Serialization.JsonConverter<T>? converter, System.Func<object, T>? getter, System.Action<object, T>? setter, System.Text.Json.Serialization.JsonIgnoreCondition ignoreCondition, System.Text.Json.Serialization.JsonNumberHandling numberHandling, string propertyName, string? jsonPropertyName) { throw null; }
         public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> CreateValueInfo<T>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.JsonConverter converter) { throw null; }
         public static System.Text.Json.Serialization.JsonConverter<T> GetEnumConverter<T>(System.Text.Json.JsonSerializerOptions options) where T : struct { throw null; }
         public static System.Text.Json.Serialization.JsonConverter<T?> GetNullableConverter<T>(System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> underlyingTypeInfo) where T : struct { throw null; }
-        public static void InitializeObjectInfo<T>(System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> info, System.Text.Json.JsonSerializerOptions options, System.Func<T>? createObjectFunc, System.Func<System.Text.Json.Serialization.JsonSerializerContext, System.Text.Json.Serialization.Metadata.JsonPropertyInfo[]> propInitFunc, System.Text.Json.Serialization.JsonNumberHandling numberHandling) where T : notnull { }
     }
     public abstract partial class JsonPropertyInfo
     {
@@ -891,5 +915,6 @@ namespace System.Text.Json.Serialization.Metadata
     public abstract partial class JsonTypeInfo<T> : System.Text.Json.Serialization.Metadata.JsonTypeInfo
     {
         internal JsonTypeInfo() { }
+        public System.Action<System.Text.Json.Utf8JsonWriter, T>? Serialize { get { throw null; } }
     }
 }
index c68c367..9f73cb0 100644 (file)
   <data name="NodeJsonObjectCustomConverterNotAllowedOnExtensionProperty" xml:space="preserve">
     <value>A custom converter for JsonObject is not allowed on an extension property.</value>
   </data>
+  <data name="PropInitAndSerializeFuncsNull" xml:space="preserve">
+    <value>'propInitFunc' and 'serializeFunc' cannot both be 'null'. </value>
+  </data>
+  <data name="NoMetadataForTypeProperties" xml:space="preserve">
+    <value>'JsonSerializerContext' '{0}' did not provide property metadata for type '{1}'.</value>
+  </data>
+  <data name="NoDefaultOptionsForContext" xml:space="preserve">
+    <value>To specify a serialization implementation for type '{0}'', context '{0}' must specify default options.</value>
+  </data>
 </root>
\ No newline at end of file
index 90c3695..ed42782 100644 (file)
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <TargetFrameworks>$(NetCoreAppCurrent);netstandard2.0;netcoreapp3.0;net461</TargetFrameworks>
   <ItemGroup>
     <Compile Include="$(CommonPath)System\HexConverter.cs" Link="Common\System\HexConverter.cs" />
     <Compile Include="$(CommonPath)System\Text\Json\PooledByteBufferWriter.cs" Link="Common\System\Text\Json\PooledByteBufferWriter.cs" />
+    <Compile Include="..\Common\JsonCamelCaseNamingPolicy.cs" Link="Common\System\Text\Json\JsonCamelCaseNamingPolicy.cs" />
+    <Compile Include="..\Common\JsonNamingPolicy.cs" Link="Common\System\Text\Json\JsonNamingPolicy.cs" />
+    <Compile Include="..\Common\JsonAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonAttribute.cs" />
     <Compile Include="..\Common\JsonIgnoreCondition.cs" Link="Common\System\Text\Json\Serialization\JsonIgnoreCondition.cs" />
+    <Compile Include="..\Common\JsonKnownNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonKnownNamingPolicy.cs" />
     <Compile Include="..\Common\JsonNumberHandling.cs" Link="Common\System\Text\Json\Serialization\JsonNumberHandling.cs" />
+    <Compile Include="..\Common\JsonSerializerOptionsAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonSerializerOptionsAttribute.cs" />
+    <Compile Include="..\Common\JsonSourceGenerationMode.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationMode.cs" />
     <Compile Include="System\Text\Json\BitStack.cs" />
     <Compile Include="System\Text\Json\Document\JsonDocument.cs" />
     <Compile Include="System\Text\Json\Document\JsonDocument.DbRow.cs" />
@@ -78,7 +84,6 @@
     <Compile Include="System\Text\Json\Reader\Utf8JsonReader.TryGet.cs" />
     <Compile Include="System\Text\Json\Serialization\Arguments.cs" />
     <Compile Include="System\Text\Json\Serialization\ArgumentState.cs" />
-    <Compile Include="System\Text\Json\Serialization\Attributes\JsonAttribute.cs" />
     <Compile Include="System\Text\Json\Serialization\Attributes\JsonConstructorAttribute.cs" />
     <Compile Include="System\Text\Json\Serialization\Attributes\JsonConverterAttribute.cs" />
     <Compile Include="System\Text\Json\Serialization\Attributes\JsonExtensionDataAttribute.cs" />
@@ -87,7 +92,7 @@
     <Compile Include="System\Text\Json\Serialization\Attributes\JsonNumberHandlingAttribute.cs" />
     <Compile Include="System\Text\Json\Serialization\Attributes\JsonPropertyNameAttribute.cs" />
     <Compile Include="System\Text\Json\Serialization\Attributes\JsonSerializableAttribute.cs" />
-    <Compile Include="System\Text\Json\Serialization\Converters\Object\ObjectSourceGenConverter.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonMetadataServicesConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\IgnoreReferenceResolver.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonSerializerContext.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.Collections.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\Value\UriConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\Value\VersionConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\IgnoreReferenceHandler.cs" />
-    <Compile Include="System\Text\Json\Serialization\JsonCamelCaseNamingPolicy.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonConverter.ReadAhead.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonConverterFactory.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonConverterOfT.ReadCore.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonConverterOfT.WriteCore.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonConverterOfT.cs" />
-    <Compile Include="System\Text\Json\Serialization\JsonDefaultNamingPolicy.cs" />
-    <Compile Include="System\Text\Json\Serialization\JsonNamingPolicy.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonResumableConverterOfT.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonSerializer.Helpers.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleMetadata.cs" />
index 9b58024..b223fad 100644 (file)
@@ -9,10 +9,16 @@ namespace System.Text.Json.Serialization
     /// Instructs the System.Text.Json source generator to generate source code to help optimize performance
     /// when serializing and deserializing instances of the specified type and types in its object graph.
     /// </summary>
-    [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
     public sealed class JsonSerializableAttribute : JsonAttribute
     {
         /// <summary>
+        /// Initializes a new instance of <see cref="JsonSerializableAttribute"/> with the specified type.
+        /// </summary>
+        /// <param name="type">The type to generate source code for.</param>
+        public JsonSerializableAttribute(Type type) { }
+
+        /// <summary>
         /// The name of the property for the generated <see cref="JsonTypeInfo{T}"/> for
         /// the type on the generated, derived <see cref="JsonSerializerContext"/> type.
         /// </summary>
@@ -22,9 +28,8 @@ namespace System.Text.Json.Serialization
         public string? TypeInfoPropertyName { get; set; }
 
         /// <summary>
-        /// Initializes a new instance of <see cref="JsonSerializableAttribute"/> with the specified type.
+        /// Determines what the source generator should generate for the type.
         /// </summary>
-        /// <param name="type">The type to generate source code for.</param>
-        public JsonSerializableAttribute(Type type) { }
+        public JsonSourceGenerationMode GenerationMode { get; set; }
     }
 }
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs
new file mode 100644 (file)
index 0000000..d28134e
--- /dev/null
@@ -0,0 +1,90 @@
+// 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.Text.Json.Serialization.Metadata;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    /// <summary>
+    /// Provides a mechanism to invoke "fast-path" serialization logic via
+    /// <see cref="JsonTypeInfo{T}.Serialize"/>. This type holds an optional
+    /// reference to an actual <see cref="JsonConverter{T}"/> for the type
+    /// <typeparamref name="T"/>, to provide a fallback when the fast path cannot be used.
+    /// </summary>
+    /// <typeparam name="T">The type to converter</typeparam>
+    internal sealed class JsonMetadataServicesConverter<T> : JsonResumableConverter<T>
+    {
+        private readonly Func<JsonConverter<T>> _converterCreator;
+
+        private readonly ConverterStrategy _converterStrategy;
+
+        private readonly Type? _keyType;
+        private readonly Type? _elementType;
+
+        private JsonConverter<T>? _converter;
+
+        // A backing converter for when fast-path logic cannot be used.
+        private JsonConverter<T> Converter
+        {
+            get
+            {
+                _converter ??= _converterCreator();
+                Debug.Assert(_converter != null);
+                Debug.Assert(_converter.ConverterStrategy == _converterStrategy);
+                Debug.Assert(_converter.KeyType == _keyType);
+                Debug.Assert(_converter.ElementType == _elementType);
+                return _converter;
+            }
+        }
+
+        internal override ConverterStrategy ConverterStrategy => _converterStrategy;
+
+        internal override Type? KeyType => _keyType;
+
+        internal override Type? ElementType => _elementType;
+
+        public JsonMetadataServicesConverter(Func<JsonConverter<T>> converterCreator, ConverterStrategy converterStrategy, Type? keyType, Type? elementType)
+        {
+            _converterCreator = converterCreator ?? throw new ArgumentNullException(nameof(converterCreator));
+            _converterStrategy = converterStrategy;
+            _keyType = keyType;
+            _elementType = elementType;
+        }
+
+        internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, out T? value)
+        {
+            JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
+
+            if (_converterStrategy == ConverterStrategy.Object && jsonTypeInfo.PropertyCache == null)
+            {
+                jsonTypeInfo.InitializeDeserializePropCache();
+            }
+
+            return Converter.OnTryRead(ref reader, typeToConvert, options, ref state, out value);
+        }
+
+        internal override bool OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializerOptions options, ref WriteStack state)
+        {
+            JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
+
+            Debug.Assert(options == jsonTypeInfo.Options);
+
+            if (!state.SupportContinuation &&
+                jsonTypeInfo is JsonTypeInfo<T> info &&
+                info.Serialize != null &&
+                info.Options._context?.CanUseSerializationLogic == true)
+            {
+                info.Serialize(writer, value);
+                return true;
+            }
+
+            if (_converterStrategy == ConverterStrategy.Object && jsonTypeInfo.PropertyCacheArray == null)
+            {
+                jsonTypeInfo.InitializeSerializePropCache();
+            }
+
+            return Converter.OnTryWrite(writer, value, options, ref state);
+        }
+    }
+}
index 63f48da..234c2c2 100644 (file)
@@ -15,6 +15,8 @@ namespace System.Text.Json.Serialization.Converters
     {
         internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, [MaybeNullWhen(false)] out T value)
         {
+            JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
+
             object obj;
 
             if (state.UseFastPath)
@@ -26,12 +28,12 @@ namespace System.Text.Json.Serialization.Converters
                     ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
                 }
 
-                if (state.Current.JsonTypeInfo.CreateObject == null)
+                if (jsonTypeInfo.CreateObject == null)
                 {
-                    ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(state.Current.JsonTypeInfo.Type, ref reader, ref state);
+                    ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(jsonTypeInfo.Type, ref reader, ref state);
                 }
 
-                obj = state.Current.JsonTypeInfo.CreateObject!()!;
+                obj = jsonTypeInfo.CreateObject!()!;
 
                 // Process all properties.
                 while (true)
@@ -98,12 +100,12 @@ namespace System.Text.Json.Serialization.Converters
 
                 if (state.Current.ObjectState < StackFrameObjectState.CreatedObject)
                 {
-                    if (state.Current.JsonTypeInfo.CreateObject == null)
+                    if (jsonTypeInfo.CreateObject == null)
                     {
-                        ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(state.Current.JsonTypeInfo.Type, ref reader, ref state);
+                        ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(jsonTypeInfo.Type, ref reader, ref state);
                     }
 
-                    obj = state.Current.JsonTypeInfo.CreateObject!()!;
+                    obj = jsonTypeInfo.CreateObject!()!;
 
                     state.Current.ReturnValue = obj;
                     state.Current.ObjectState = StackFrameObjectState.CreatedObject;
@@ -216,7 +218,7 @@ namespace System.Text.Json.Serialization.Converters
             // Check if we are trying to build the sorted cache.
             if (state.Current.PropertyRefCache != null)
             {
-                state.Current.JsonTypeInfo.UpdateSortedPropertyCache(ref state.Current);
+                jsonTypeInfo.UpdateSortedPropertyCache(ref state.Current);
             }
 
             value = (T)obj;
@@ -224,12 +226,14 @@ namespace System.Text.Json.Serialization.Converters
             return true;
         }
 
-        internal override bool OnTryWrite(
+        internal sealed override bool OnTryWrite(
             Utf8JsonWriter writer,
             T value,
             JsonSerializerOptions options,
             ref WriteStack state)
         {
+            JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
+
             // Minimize boxing for structs by only boxing once here
             object objectValue = value!;
 
@@ -244,10 +248,10 @@ namespace System.Text.Json.Serialization.Converters
                     }
                 }
 
-                JsonPropertyInfo? dataExtensionProperty = state.Current.JsonTypeInfo.DataExtensionProperty;
+                JsonPropertyInfo? dataExtensionProperty = jsonTypeInfo.DataExtensionProperty;
 
                 int propertyCount = 0;
-                JsonPropertyInfo[]? propertyCacheArray = state.Current.JsonTypeInfo.PropertyCacheArray;
+                JsonPropertyInfo[]? propertyCacheArray = jsonTypeInfo.PropertyCacheArray;
                 if (propertyCacheArray != null)
                 {
                     propertyCount = propertyCacheArray.Length;
@@ -302,10 +306,10 @@ namespace System.Text.Json.Serialization.Converters
                     state.Current.ProcessedStartToken = true;
                 }
 
-                JsonPropertyInfo? dataExtensionProperty = state.Current.JsonTypeInfo.DataExtensionProperty;
+                JsonPropertyInfo? dataExtensionProperty = jsonTypeInfo.DataExtensionProperty;
 
                 int propertyCount = 0;
-                JsonPropertyInfo[]? propertyCacheArray = state.Current.JsonTypeInfo.PropertyCacheArray;
+                JsonPropertyInfo[]? propertyCacheArray = jsonTypeInfo.PropertyCacheArray;
                 if (propertyCacheArray != null)
                 {
                     propertyCount = propertyCacheArray.Length;
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectSourceGenConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectSourceGenConverter.cs
deleted file mode 100644 (file)
index 70f377d..0000000
+++ /dev/null
@@ -1,46 +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;
-using System.Diagnostics.CodeAnalysis;
-using System.Text.Json.Serialization.Metadata;
-
-namespace System.Text.Json.Serialization.Converters
-{
-    /// <summary>
-    /// Implementation of <cref>JsonObjectConverter{T}</cref> for source-generated converters.
-    /// </summary>
-    internal sealed class ObjectSourceGenConverter<T> : ObjectDefaultConverter<T> where T : notnull
-    {
-        internal override bool OnTryRead(
-            ref Utf8JsonReader reader,
-            Type typeToConvert,
-            JsonSerializerOptions options,
-            ref ReadStack state,
-            [MaybeNullWhen(false)] out T value)
-        {
-            JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
-            if (jsonTypeInfo.PropertyCache == null)
-            {
-                jsonTypeInfo.InitializeDeserializePropCache();
-            }
-
-            return base.OnTryRead(ref reader, typeToConvert, options, ref state, out value);
-        }
-
-        internal override bool OnTryWrite(
-            Utf8JsonWriter writer,
-            T value,
-            JsonSerializerOptions options,
-            ref WriteStack state)
-        {
-            JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
-            if (jsonTypeInfo.PropertyCacheArray == null)
-            {
-                jsonTypeInfo.InitializeSerializePropCache();
-            }
-
-            return base.OnTryWrite(writer, value, options, ref state);
-        }
-    }
-}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonDefaultNamingPolicy.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonDefaultNamingPolicy.cs
deleted file mode 100644 (file)
index 300096f..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace System.Text.Json
-{
-    internal sealed class JsonDefaultNamingPolicy : JsonNamingPolicy
-    {
-        public override string ConvertName(string name) => name;
-    }
-}
index e1d7eb1..ccf639b 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.Diagnostics.CodeAnalysis;
-
 namespace System.Text.Json.Serialization
 {
     /// <summary>
index f212494..50c5eac 100644 (file)
@@ -41,15 +41,24 @@ namespace System.Text.Json
 
         private static void WriteUsingMetadata<TValue>(Utf8JsonWriter writer, in TValue value, JsonTypeInfo jsonTypeInfo)
         {
-            WriteStack state = default;
-            state.Initialize(jsonTypeInfo, supportContinuation: false);
+            if (jsonTypeInfo is JsonTypeInfo<TValue> typedInfo &&
+                typedInfo.Serialize != null &&
+                typedInfo.Options._context?.CanUseSerializationLogic == true)
+            {
+                typedInfo.Serialize(writer, value);
+            }
+            else
+            {
+                WriteStack state = default;
+                state.Initialize(jsonTypeInfo, supportContinuation: false);
 
-            JsonConverter converter = jsonTypeInfo.PropertyInfoForTypeInfo.ConverterBase;
-            Debug.Assert(converter != null);
+                JsonConverter converter = jsonTypeInfo.PropertyInfoForTypeInfo.ConverterBase;
+                Debug.Assert(converter != null);
 
-            Debug.Assert(jsonTypeInfo.Options != null);
+                Debug.Assert(jsonTypeInfo.Options != null);
 
-            WriteCore(converter, writer, value, jsonTypeInfo.Options, ref state);
+                WriteCore(converter, writer, value, jsonTypeInfo.Options, ref state);
+            }
         }
 
         private static Type GetRuntimeType<TValue>(in TValue value)
index 12dd595..78843cf 100644 (file)
@@ -12,9 +12,48 @@ namespace System.Text.Json.Serialization
     [EditorBrowsable(EditorBrowsableState.Never)]
     public abstract partial class JsonSerializerContext
     {
+        private bool? _canUseSerializationLogic;
+        private JsonSerializerOptions? _defaultOptions;
+
         internal JsonSerializerOptions? _options;
 
         /// <summary>
+        /// Indicates whether pre-generated serialization logic for types in the context
+        /// is compatible with the run-time specified <see cref="JsonSerializerOptions"/>.
+        /// </summary>
+        internal bool CanUseSerializationLogic
+        {
+            get
+            {
+                if (!_canUseSerializationLogic.HasValue)
+                {
+                    if (_defaultOptions == null)
+                    {
+                        _canUseSerializationLogic = false;
+                    }
+                    else
+                    {
+                        _canUseSerializationLogic =
+                            // Guard against unsupported features
+                            Options.Converters.Count == 0 &&
+                            Options.Encoder == null &&
+                            Options.NumberHandling == JsonNumberHandling.Strict &&
+                            Options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.None &&
+                            // Ensure options values are consistent with expected defaults.
+                            Options.DefaultIgnoreCondition == _defaultOptions.DefaultIgnoreCondition &&
+                            Options.IgnoreReadOnlyFields == _defaultOptions.IgnoreReadOnlyFields &&
+                            Options.IgnoreReadOnlyProperties == _defaultOptions.IgnoreReadOnlyProperties &&
+                            Options.IncludeFields == _defaultOptions.IncludeFields &&
+                            Options.PropertyNamingPolicy == _defaultOptions.PropertyNamingPolicy &&
+                            Options.WriteIndented == _defaultOptions.WriteIndented;
+                    }
+                }
+
+                return _canUseSerializationLogic.Value;
+            }
+        }
+
+        /// <summary>
         /// Gets the run-time specified options of the context. If no options were passed
         /// when instanciating the context, then a new instance is bound and returned.
         /// </summary>
@@ -38,23 +77,26 @@ namespace System.Text.Json.Serialization
         /// <summary>
         /// Creates an instance of <see cref="JsonSerializerContext"/> and binds it with the indicated <see cref="JsonSerializerOptions"/>.
         /// </summary>
-        /// <param name="options">The run-time provided options for the context instance.</param>
+        /// <param name="instanceOptions">The run-time provided options for the context instance.</param>
+        /// <param name="defaultOptions">The default run-time options for the context. It's values are defined at design-time via <see cref="JsonSerializerOptionsAttribute"/>.</param>
         /// <remarks>
-        /// If no options are passed, then no options are set until the context is bound using <see cref="JsonSerializerOptions.AddContext{TContext}"/>,
+        /// If no instance options are passed, then no options are set until the context is bound using <see cref="JsonSerializerOptions.AddContext{TContext}"/>,
         /// or until <see cref="Options"/> is called, where a new options instance is created and bound.
         /// </remarks>
-        protected JsonSerializerContext(JsonSerializerOptions? options)
+        protected JsonSerializerContext(JsonSerializerOptions? instanceOptions, JsonSerializerOptions? defaultOptions)
         {
-            if (options != null)
+            if (instanceOptions != null)
             {
-                if (options._context != null)
+                if (instanceOptions._context != null)
                 {
                     ThrowHelper.ThrowInvalidOperationException_JsonSerializerOptionsAlreadyBoundToContext();
                 }
 
-                _options = options;
-                options._context = this;
+                _options = instanceOptions;
+                instanceOptions._context = this;
             }
+
+            _defaultOptions = defaultOptions;
         }
 
         /// <summary>
index e4782e9..12f3717 100644 (file)
@@ -15,17 +15,21 @@ namespace System.Text.Json.Serialization.Metadata
         /// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
         /// <param name="elementInfo">A <see cref="JsonTypeInfo"/> instance representing the element type.</param>
         /// <param name="numberHandling">The <see cref="JsonNumberHandling"/> option to apply to number collection elements.</param>
+        /// <param name="serializeFunc">An optimized serialization implementation assuming pre-determined <see cref="JsonSerializerOptionsAttribute"/> defaults.</param>
         /// <returns></returns>
         public static JsonTypeInfo<TElement[]> CreateArrayInfo<TElement>(
             JsonSerializerOptions options,
             JsonTypeInfo elementInfo,
-            JsonNumberHandling numberHandling)
+            JsonNumberHandling numberHandling,
+            Action<Utf8JsonWriter, TElement[]>? serializeFunc)
             => new JsonTypeInfoInternal<TElement[]>(
                 options,
                 createObjectFunc: null,
-                new ArrayConverter<TElement[], TElement>(),
+                () => new ArrayConverter<TElement[], TElement>(),
                 elementInfo,
-                numberHandling);
+                numberHandling,
+                serializeFunc,
+                typeof(TElement));
 
         /// <summary>
         /// Creates metadata for types assignable to <see cref="List{T}"/>.
@@ -36,19 +40,23 @@ namespace System.Text.Json.Serialization.Metadata
         /// <param name="createObjectFunc">A <see cref="Func{TResult}"/> to create an instance of the list when deserializing.</param>
         /// <param name="elementInfo">A <see cref="JsonTypeInfo"/> instance representing the element type.</param>
         /// <param name="numberHandling">The <see cref="JsonNumberHandling"/> option to apply to number collection elements.</param>
+        /// <param name="serializeFunc">An optimized serialization implementation assuming pre-determined <see cref="JsonSerializerOptionsAttribute"/> defaults.</param>
         /// <returns></returns>
         public static JsonTypeInfo<TCollection> CreateListInfo<TCollection, TElement>(
             JsonSerializerOptions options,
             Func<TCollection>? createObjectFunc,
             JsonTypeInfo elementInfo,
-            JsonNumberHandling numberHandling)
+            JsonNumberHandling numberHandling,
+            Action<Utf8JsonWriter, TCollection>? serializeFunc)
             where TCollection : List<TElement>
             => new JsonTypeInfoInternal<TCollection>(
                 options,
                 createObjectFunc,
-                new ListOfTConverter<TCollection, TElement>(),
+                () => new ListOfTConverter<TCollection, TElement>(),
                 elementInfo,
-                numberHandling);
+                numberHandling,
+                serializeFunc,
+                typeof(TElement));
 
         /// <summary>
         /// Creates metadata for types assignable to <see cref="Dictionary{TKey, TValue}"/>.
@@ -61,21 +69,26 @@ namespace System.Text.Json.Serialization.Metadata
         /// <param name="keyInfo">A <see cref="JsonTypeInfo"/> instance representing the key type.</param>
         /// <param name="valueInfo">A <see cref="JsonTypeInfo"/> instance representing the value type.</param>
         /// <param name="numberHandling">The <see cref="JsonNumberHandling"/> option to apply to number collection elements.</param>
+        /// <param name="serializeFunc">An optimized serialization implementation assuming pre-determined <see cref="JsonSerializerOptionsAttribute"/> defaults.</param>
         /// <returns></returns>
         public static JsonTypeInfo<TCollection> CreateDictionaryInfo<TCollection, TKey, TValue>(
             JsonSerializerOptions options,
             Func<TCollection> createObjectFunc,
             JsonTypeInfo keyInfo,
             JsonTypeInfo valueInfo,
-            JsonNumberHandling numberHandling)
+            JsonNumberHandling numberHandling,
+            Action<Utf8JsonWriter, TCollection>? serializeFunc)
             where TCollection : Dictionary<TKey, TValue>
             where TKey : notnull
             => new JsonTypeInfoInternal<TCollection>(
                 options,
                 createObjectFunc,
-                new DictionaryOfTKeyTValueConverter<TCollection, TKey, TValue>(),
+                () => new DictionaryOfTKeyTValueConverter<TCollection, TKey, TValue>(),
                 keyInfo,
                 valueInfo,
-                numberHandling);
+                numberHandling,
+                serializeFunc,
+                typeof(TKey),
+                typeof(TValue));
     }
 }
index 681a201..0be5273 100644 (file)
@@ -147,7 +147,7 @@ namespace System.Text.Json.Serialization.Metadata
         /// Creates a <see cref="JsonConverter{T}"/> instance that converts <typeparamref name="T"/> values.
         /// </summary>
         /// <typeparam name="T">The generic definition for the enum type.</typeparam>
-        /// <param name="options"></param>
+        /// <param name="options">The <see cref="JsonSerializerOptions"/> to use for serialization and deserialization.</param>
         /// <returns></returns>
         public static JsonConverter<T> GetEnumConverter<T>(JsonSerializerOptions options) where T : struct, Enum
             => new EnumConverter<T>(EnumConverterOptions.AllowNumbers, options ?? throw new ArgumentNullException(nameof(options)));
@@ -155,8 +155,8 @@ namespace System.Text.Json.Serialization.Metadata
         /// <summary>
         /// Creates a <see cref="JsonConverter{T}"/> instance that converts <typeparamref name="T?"/> values.
         /// </summary>
-        /// <typeparam name="T"></typeparam>
-        /// <param name="underlyingTypeInfo"></param>
+        /// <typeparam name="T">The generic definition for the underlying nullable type.</typeparam>
+        /// <param name="underlyingTypeInfo">Serialization metadata for the underlying nullable type.</param>
         /// <returns></returns>
         public static JsonConverter<T?> GetNullableConverter<T>(JsonTypeInfo<T> underlyingTypeInfo) where T : struct
         {
index 27a2fe1..14fd403 100644 (file)
@@ -16,6 +16,17 @@ namespace System.Text.Json.Serialization.Metadata
         /// Creates metadata for a property or field.
         /// </summary>
         /// <typeparam name="T">The type that the converter for the property returns or accepts when converting JSON data.</typeparam>
+        /// <param name="options">The <see cref="JsonSerializerOptions"/> to initialize the metadata with.</param>
+        /// <param name="isProperty">Whether the CLR member is a property or field.</param>
+        /// <param name="declaringType">The declaring type of the property or field.</param>
+        /// <param name="propertyTypeInfo">The <see cref="JsonTypeInfo"/> info for the property or field's type.</param>
+        /// <param name="converter">A <see cref="JsonConverter"/> for the property or field, specified by <see cref="JsonConverterAttribute"/>.</param>
+        /// <param name="getter">Provides a mechanism to get the property or field's value.</param>
+        /// <param name="setter">Provides a mechanism to set the property or field's value.</param>
+        /// <param name="ignoreCondition">Specifies a condition for the property to be ignored.</param>
+        /// <param name="numberHandling">If the property or field is a number, specifies how it should processed when serializing and deserializing.</param>
+        /// <param name="propertyName">The CLR name of the property or field.</param>
+        /// <param name="jsonPropertyName">The name to be used when processing the property or field, specified by <see cref="JsonPropertyNameAttribute"/>.</param>
         /// <returns>A <see cref="JsonPropertyInfo"/> instance intialized with the provided metadata.</returns>
         public static JsonPropertyInfo CreatePropertyInfo<T>(
             JsonSerializerOptions options,
@@ -79,53 +90,21 @@ namespace System.Text.Json.Serialization.Metadata
         /// <summary>
         /// Creates metadata for a complex class or struct.
         /// </summary>
+        /// <param name="options">The <see cref="JsonSerializerOptions"/> to initialize the metadata with.</param>
+        /// <param name="createObjectFunc">Provides a mechanism to create an instance of the class or struct when deserializing.</param>
+        /// <param name="propInitFunc">Provides a mechanism to initialize metadata for properties and fields of the class or struct.</param>
+        /// <param name="serializeFunc">Provides a serialization implementation for instances of the class or struct which assumes options specified by <see cref="JsonSerializerOptionsAttribute"/>.</param>
+        /// <param name="numberHandling">Specifies how number properties and fields should be processed when serializing and deserializing.</param>
         /// <typeparam name="T">The type of the class or struct.</typeparam>
+        /// <exception cref="InvalidOperationException">Thrown when <paramref name="options"/> and <paramref name="propInitFunc"/> are both null.</exception>
         /// <returns>A <see cref="JsonTypeInfo{T}"/> instance representing the class or struct.</returns>
-        public static JsonTypeInfo<T> CreateObjectInfo<T>() where T : notnull => new JsonTypeInfoInternal<T>();
-
-        /// <summary>
-        /// Initializes metadata for a class or struct.
-        /// </summary>
-        /// <typeparam name="T">The type of the class or struct</typeparam>
-        /// <param name="info"></param>
-        /// <param name="options"></param>
-        /// <param name="createObjectFunc"></param>
-        /// <param name="propInitFunc"></param>
-        /// <param name="numberHandling"></param>
-        /// <exception cref="ArgumentNullException">Thrown when <paramref name="options"/>, <paramref name="info"/>, or <paramref name="propInitFunc"/> is null.</exception>
-        /// <exception cref="ArgumentException">Thrown when <paramref name="info"/>, does not represent a complex class or struct type.</exception>
-        public static void InitializeObjectInfo<T>(
-            JsonTypeInfo<T> info,
+        public static JsonTypeInfo<T> CreateObjectInfo<T>(
             JsonSerializerOptions options,
             Func<T>? createObjectFunc,
-            Func<JsonSerializerContext, JsonPropertyInfo[]> propInitFunc,
-            JsonNumberHandling numberHandling)
-            where T : notnull
-        {
-            if (info == null)
-            {
-                throw new ArgumentNullException(nameof(info));
-            }
-
-            if (info.PropertyInfoForTypeInfo != null)
-            {
-                // ConverterStrategy.Object is the only info type we won't have set PropertyInfoForTypeInfo for at this point.
-                throw new ArgumentException(SR.InitializeTypeInfoAsObjectInvalid, nameof(info));
-            }
-
-            if (options == null)
-            {
-                throw new ArgumentNullException(nameof(options));
-            }
-
-            if (propInitFunc == null)
-            {
-                throw new ArgumentNullException(nameof(propInitFunc));
-            }
-
-            ((JsonTypeInfoInternal<T>)info).InitializeAsObject(options, createObjectFunc, propInitFunc, numberHandling);
-            Debug.Assert(info.PropertyInfoForTypeInfo!.ConverterStrategy == ConverterStrategy.Object);
-        }
+            Func<JsonSerializerContext, JsonPropertyInfo[]>? propInitFunc,
+            JsonNumberHandling numberHandling,
+            Action<Utf8JsonWriter, T>? serializeFunc) where T : notnull
+            => new JsonTypeInfoInternal<T>(options, createObjectFunc, propInitFunc, numberHandling, serializeFunc);
 
         /// <summary>
         /// Creates metadata for a primitive or a type with a custom converter.
@@ -134,7 +113,7 @@ namespace System.Text.Json.Serialization.Metadata
         /// <returns>A <see cref="JsonTypeInfo{T}"/> instance representing the type.</returns>
         public static JsonTypeInfo<T> CreateValueInfo<T>(JsonSerializerOptions options, JsonConverter converter)
         {
-            JsonTypeInfo<T> info = new JsonTypeInfoInternal<T>(options);
+            JsonTypeInfo<T> info = new JsonTypeInfoInternal<T>(options, ConverterStrategy.Value);
             info.PropertyInfoForTypeInfo = CreateJsonPropertyInfoForClassInfo(typeof(T), info, converter, options);
             return info;
         }
index 39dffda..f6cbe62 100644 (file)
@@ -568,14 +568,24 @@ namespace System.Text.Json.Serialization.Metadata
 
         internal void InitializeSerializePropCache()
         {
-            Debug.Assert(PropInitFunc != null);
-            Debug.Assert(Options._context != null);
+            JsonSerializerContext? context = Options._context;
 
-            PropertyCacheArray = PropInitFunc(Options._context);
+            Debug.Assert(context != null);
+            Debug.Assert(PropertyInfoForTypeInfo.ConverterStrategy == ConverterStrategy.Object);
+
+            if (PropInitFunc == null)
+            {
+                ThrowHelper.ThrowInvalidOperationException_NoMetadataForTypeProperties(context, Type);
+                return;
+            }
+
+            PropertyCacheArray = PropInitFunc(context);
         }
 
         internal void InitializeDeserializePropCache()
         {
+            Debug.Assert(PropertyInfoForTypeInfo.ConverterStrategy == ConverterStrategy.Object);
+
             if (PropertyCacheArray == null)
             {
                 InitializeSerializePropCache();
index e61da17..2de65bf 100644 (file)
@@ -149,15 +149,8 @@ namespace System.Text.Json.Serialization.Metadata
 
         internal JsonTypeInfo(Type type, JsonSerializerOptions options, ConverterStrategy converterStrategy)
         {
-            // Options setting for object class types is deferred till initialization.
-            if (converterStrategy != ConverterStrategy.Object && options == null)
-            {
-                throw new ArgumentNullException(nameof(options));
-            }
-
-            Options = options!;
             Type = type;
-
+            Options = options ?? throw new ArgumentNullException(nameof(options));
             // Setting this option is deferred to the initialization methods of the various metadada info types.
             PropertyInfoForTypeInfo = null!;
         }
index 38fe706..b016ef9 100644 (file)
@@ -12,18 +12,40 @@ namespace System.Text.Json.Serialization.Metadata
     internal sealed class JsonTypeInfoInternal<T> : JsonTypeInfo<T>
     {
         /// <summary>
-        /// Creates serialization metadata for a <see cref="ConverterStrategy.Object"/>.
+        /// Creates serialization metadata given JsonSerializerOptions and a ConverterStrategy.
         /// </summary>
-        public JsonTypeInfoInternal() : base(typeof(T), null!, ConverterStrategy.Object)
+        public JsonTypeInfoInternal(JsonSerializerOptions options, ConverterStrategy converterStrategy)
+            : base(typeof(T), options, converterStrategy)
         {
         }
 
         /// <summary>
-        /// Creates serialization metadata for a <see cref="ConverterStrategy.Value"/>.
+        /// Creates serialization metadata for an object.
         /// </summary>
-        public JsonTypeInfoInternal(JsonSerializerOptions options)
-            : base (typeof(T), options, ConverterStrategy.Value)
+        public JsonTypeInfoInternal(
+            JsonSerializerOptions options,
+            Func<T>? createObjectFunc,
+            Func<JsonSerializerContext, JsonPropertyInfo[]>? propInitFunc,
+            JsonNumberHandling numberHandling,
+            Action<Utf8JsonWriter, T>? serializeFunc
+            ) : base(typeof(T), options, ConverterStrategy.Object)
         {
+            if (propInitFunc == null && serializeFunc == null)
+            {
+                ThrowHelper.ThrowInvalidOperationException_PropInitAndSerializeFuncsNull();
+            }
+
+#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.
+            JsonConverter converter = new JsonMetadataServicesConverter<T>(() => new ObjectDefaultConverter<T>(), ConverterStrategy.Object, keyType: null, elementType: null);
+#pragma warning restore CS8714
+
+            PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, Options);
+            NumberHandling = numberHandling;
+            PropInitFunc = propInitFunc;
+            Serialize = serializeFunc;
+            SetCreateObjectFunc(createObjectFunc);
         }
 
         /// <summary>
@@ -32,14 +54,19 @@ namespace System.Text.Json.Serialization.Metadata
         public JsonTypeInfoInternal(
             JsonSerializerOptions options,
             Func<T>? createObjectFunc,
-            JsonConverter<T> converter,
-            JsonTypeInfo elementInfo,
-            JsonNumberHandling numberHandling) : base(typeof(T), options, ConverterStrategy.Enumerable)
+            Func<JsonConverter<T>> converterCreator,
+            JsonTypeInfo? elementInfo,
+            JsonNumberHandling numberHandling,
+            Action<Utf8JsonWriter, T>? serializeFunc,
+            Type elementType) : base(typeof(T), options, ConverterStrategy.Enumerable)
         {
+            JsonConverter<T> converter = new JsonMetadataServicesConverter<T>(converterCreator, ConverterStrategy.Enumerable, keyType: null, elementType);
+
             ElementType = converter.ElementType;
             ElementTypeInfo = elementInfo ?? throw new ArgumentNullException(nameof(elementInfo));
             NumberHandling = numberHandling;
             PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, options);
+            Serialize = serializeFunc;
             SetCreateObjectFunc(createObjectFunc);
         }
 
@@ -49,40 +76,24 @@ namespace System.Text.Json.Serialization.Metadata
         public JsonTypeInfoInternal(
             JsonSerializerOptions options,
             Func<T>? createObjectFunc,
-            JsonConverter<T> converter,
-            JsonTypeInfo keyInfo,
-            JsonTypeInfo valueInfo,
-            JsonNumberHandling numberHandling) : base(typeof(T), options, ConverterStrategy.Dictionary)
+            Func<JsonConverter<T>> converterCreator,
+            JsonTypeInfo? keyInfo,
+            JsonTypeInfo? valueInfo,
+            JsonNumberHandling numberHandling,
+            Action<Utf8JsonWriter, T>? serializeFunc,
+            Type keyType,
+            Type elementType) : base(typeof(T), options, ConverterStrategy.Dictionary)
         {
+            JsonConverter<T> converter = new JsonMetadataServicesConverter<T>(converterCreator, ConverterStrategy.Dictionary, keyType, elementType);
+
             KeyType = converter.KeyType;
-            KeyTypeInfo = keyInfo ?? throw new ArgumentNullException(nameof(keyInfo)); ;
+            ElementType = converter.ElementType;
+            KeyTypeInfo = keyInfo ?? throw new ArgumentNullException(nameof(keyInfo));
             ElementType = converter.ElementType;
             ElementTypeInfo = valueInfo ?? throw new ArgumentNullException(nameof(valueInfo));
             NumberHandling = numberHandling;
             PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, options);
-            SetCreateObjectFunc(createObjectFunc);
-        }
-
-        /// <summary>
-        /// Initializes serialization metadata for a <see cref="ConverterStrategy.Object"/>.
-        /// </summary>
-        public void InitializeAsObject(
-            JsonSerializerOptions options,
-            Func<T>? createObjectFunc,
-            Func<JsonSerializerContext, JsonPropertyInfo[]> propInitFunc,
-            JsonNumberHandling numberHandling)
-        {
-            Options = options;
-
-#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.
-            JsonConverter converter = new ObjectSourceGenConverter<T>();
-#pragma warning restore CS8714
-
-            PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, options);
-            NumberHandling = numberHandling;
-            PropInitFunc = propInitFunc;
+            Serialize = serializeFunc;
             SetCreateObjectFunc(createObjectFunc);
         }
 
index 6d3b7f0..208ac31 100644 (file)
@@ -21,5 +21,11 @@ namespace System.Text.Json.Serialization.Metadata
         {
             Debug.Assert(false, "This constructor should not be called.");
         }
+
+        /// <summary>
+        /// A method that serializes an instance of <typeparamref name="T"/> using
+        /// <see cref="JsonSerializerOptionsAttribute"/> values specified at design time.
+        /// </summary>
+        public Action<Utf8JsonWriter, T>? Serialize { get; private protected set; }
     }
 }
index ab7cf89..7beb061 100644 (file)
@@ -699,5 +699,22 @@ namespace System.Text.Json
         {
             throw new InvalidOperationException(SR.Format(SR.NoMetadataForType, type));
         }
+
+        [DoesNotReturn]
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static void ThrowInvalidOperationException_PropInitAndSerializeFuncsNull()
+        {
+            throw new InvalidOperationException(SR.Format(SR.PropInitAndSerializeFuncsNull));
+        }
+
+        public static void ThrowInvalidOperationException_NoMetadataForTypeProperties(JsonSerializerContext context, Type type)
+        {
+            throw new InvalidOperationException(SR.Format(SR.NoMetadataForTypeProperties, context.GetType(), type));
+        }
+
+        public static void ThrowInvalidOperationException_NoDefaultOptionsForContext(JsonSerializerContext context, Type type)
+        {
+            throw new InvalidOperationException(SR.Format(SR.NoDefaultOptionsForContext, context.GetType(), type));
+        }
     }
 }
diff --git a/src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs b/src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs
new file mode 100644 (file)
index 0000000..8d53fc3
--- /dev/null
@@ -0,0 +1,71 @@
+// 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 Xunit;
+
+namespace System.Text.Json
+{
+    internal static partial class JsonTestHelper
+    {
+        public static void AssertJsonEqual(string expected, string actual)
+        {
+            using JsonDocument expectedDom = JsonDocument.Parse(expected);
+            using JsonDocument actualDom = JsonDocument.Parse(actual);
+            AssertJsonEqual(expectedDom.RootElement, actualDom.RootElement);
+        }
+
+        private static void AssertJsonEqual(JsonElement expected, JsonElement actual)
+        {
+            JsonValueKind valueKind = expected.ValueKind;
+            Assert.Equal(valueKind, actual.ValueKind);
+
+            switch (valueKind)
+            {
+                case JsonValueKind.Object:
+                    var propertyNames = new HashSet<string>();
+
+                    foreach (JsonProperty property in expected.EnumerateObject())
+                    {
+                        propertyNames.Add(property.Name);
+                    }
+
+                    foreach (JsonProperty property in actual.EnumerateObject())
+                    {
+                        propertyNames.Add(property.Name);
+                    }
+
+                    foreach (string name in propertyNames)
+                    {
+                        AssertJsonEqual(expected.GetProperty(name), actual.GetProperty(name));
+                    }
+                    break;
+                case JsonValueKind.Array:
+                    JsonElement.ArrayEnumerator expectedEnumerator = actual.EnumerateArray();
+                    JsonElement.ArrayEnumerator actualEnumerator = expected.EnumerateArray();
+
+                    while (expectedEnumerator.MoveNext())
+                    {
+                        Assert.True(actualEnumerator.MoveNext());
+                        AssertJsonEqual(expectedEnumerator.Current, actualEnumerator.Current);
+                    }
+
+                    Assert.False(actualEnumerator.MoveNext());
+                    break;
+                case JsonValueKind.String:
+                    Assert.Equal(expected.GetString(), actual.GetString());
+                    break;
+                case JsonValueKind.Number:
+                case JsonValueKind.True:
+                case JsonValueKind.False:
+                case JsonValueKind.Null:
+                    Assert.Equal(expected.GetRawText(), actual.GetRawText());
+                    break;
+                default:
+                    Debug.Fail($"Unexpected JsonValueKind: JsonValueKind.{valueKind}.");
+                    break;
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs
new file mode 100644 (file)
index 0000000..e7cde22
--- /dev/null
@@ -0,0 +1,91 @@
+// 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;
+using System.Text.Json.Serialization.Metadata;
+
+namespace System.Text.Json.SourceGeneration.Tests
+{
+    public interface ITestContext
+    {
+        public JsonTypeInfo<Location> Location { get; }
+        public JsonTypeInfo<RepeatedTypes.Location> RepeatedLocation { get; }
+        public JsonTypeInfo<ActiveOrUpcomingEvent> ActiveOrUpcomingEvent { get; }
+        public JsonTypeInfo<CampaignSummaryViewModel> CampaignSummaryViewModel { get; }
+        public JsonTypeInfo<IndexViewModel> IndexViewModel { get; }
+        public JsonTypeInfo<WeatherForecastWithPOCOs> WeatherForecastWithPOCOs { get; }
+        public JsonTypeInfo<EmptyPoco> EmptyPoco { get; }
+        public JsonTypeInfo<HighLowTemps> HighLowTemps { get; }
+        public JsonTypeInfo<MyType> MyType { get; }
+        public JsonTypeInfo<MyType2> MyType2 { get; }
+        public JsonTypeInfo<MyIntermediateType> MyIntermediateType { get; }
+        public JsonTypeInfo<HighLowTempsImmutable> HighLowTempsImmutable { get; }
+        public JsonTypeInfo<RealWorldContextTests.MyNestedClass> MyNestedClass { get; }
+        public JsonTypeInfo<RealWorldContextTests.MyNestedClass.MyNestedNestedClass> MyNestedNestedClass { get; }
+        public JsonTypeInfo<object[]> ObjectArray { get; }
+        public JsonTypeInfo<string> String { get; }
+        public JsonTypeInfo<RealWorldContextTests.ClassWithEnumAndNullable> ClassWithEnumAndNullable { get; }
+    }
+
+    internal partial class JsonContext : JsonSerializerContext
+    {
+        private static JsonSerializerOptions s_defaultOptions { get; } = new JsonSerializerOptions()
+        {
+            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
+            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+        };
+
+        private static JsonContext s_defaultContext;
+        public static JsonContext Default => s_defaultContext ??= new JsonContext(new JsonSerializerOptions(s_defaultOptions));
+
+        public JsonContext() : base(null, s_defaultOptions)
+        {
+        }
+
+        public JsonContext(JsonSerializerOptions options) : base(options, s_defaultOptions)
+        {
+        }
+
+        public override JsonTypeInfo GetTypeInfo(global::System.Type type)
+        {
+            if (type == typeof(JsonMessage))
+            {
+                return JsonMessage;
+            }
+
+            return null!;
+        }
+
+        private JsonTypeInfo<JsonMessage> _JsonMessage;
+        public JsonTypeInfo<JsonMessage> JsonMessage
+        {
+            get
+            {
+                if (_JsonMessage == null)
+                {
+                    JsonTypeInfo<JsonMessage> objectInfo = JsonMetadataServices.CreateObjectInfo<JsonMessage>(
+                        Options,
+                        createObjectFunc: static () => new JsonMessage(),
+                        propInitFunc: null,
+                        default,
+                        serializeFunc: JsonMessageSerialize);
+
+                    _JsonMessage = objectInfo;
+                }
+
+                return _JsonMessage;
+            }
+        }
+
+        private static void JsonMessageSerialize(Utf8JsonWriter writer, JsonMessage value) => throw new NotImplementedException();
+    }
+
+    [JsonSerializable(typeof(Dictionary<string, string>))]
+    [JsonSerializable(typeof(Dictionary<int, string>))]
+    [JsonSerializable(typeof(Dictionary<string, JsonMessage>))]
+    internal partial class DictionaryTypeContext : JsonSerializerContext { }
+
+    [JsonSerializable(typeof(JsonMessage))]
+    public partial class PublicContext : JsonSerializerContext { }
+}
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs
new file mode 100644 (file)
index 0000000..77882f6
--- /dev/null
@@ -0,0 +1,30 @@
+// 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;
+using Xunit;
+
+namespace System.Text.Json.SourceGeneration.Tests
+{
+    public static partial class JsonSerializerContextTests
+    {
+        [Fact]
+        public static void VariousNestingAndVisibilityLevelsAreSupported()
+        {
+            Assert.NotNull(PublicContext.Default);
+            Assert.NotNull(NestedContext.Default);
+            Assert.NotNull(NestedPublicContext.Default);
+            Assert.NotNull(NestedPublicContext.NestedProtectedInternalClass.Default);
+        }
+
+        [JsonSerializable(typeof(JsonMessage))]
+        internal partial class NestedContext : JsonSerializerContext { }
+
+        [JsonSerializable(typeof(JsonMessage))]
+        public partial class NestedPublicContext : JsonSerializerContext
+        {
+            [JsonSerializable(typeof(JsonMessage))]
+            protected internal partial class NestedProtectedInternalClass : JsonSerializerContext { }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonTestHelper.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonTestHelper.cs
new file mode 100644 (file)
index 0000000..66b2020
--- /dev/null
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Xunit;
+
+namespace System.Text.Json
+{
+    internal static partial class JsonTestHelper
+    {
+        internal static void AssertThrows_PropMetadataInit(Action action, Type type)
+        {
+            var ex = Assert.Throws<InvalidOperationException>(action);
+            string exAsStr = ex.ToString();
+            Assert.Contains(type.ToString(), exAsStr);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs
new file mode 100644 (file)
index 0000000..a5ffbfb
--- /dev/null
@@ -0,0 +1,57 @@
+// 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;
+using Xunit;
+
+namespace System.Text.Json.SourceGeneration.Tests
+{
+    [JsonSerializable(typeof(Location))]
+    [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation")]
+    [JsonSerializable(typeof(ActiveOrUpcomingEvent))]
+    [JsonSerializable(typeof(CampaignSummaryViewModel))]
+    [JsonSerializable(typeof(IndexViewModel))]
+    [JsonSerializable(typeof(WeatherForecastWithPOCOs))]
+    [JsonSerializable(typeof(EmptyPoco))]
+    // Ensure no errors when type of member in previously specified object graph is passed as input type to generator.
+    [JsonSerializable(typeof(HighLowTemps))]
+    [JsonSerializable(typeof(MyType))]
+    [JsonSerializable(typeof(MyType2))]
+    [JsonSerializable(typeof(MyIntermediateType))]
+    [JsonSerializable(typeof(HighLowTempsImmutable))]
+    [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass))]
+    [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass))]
+    [JsonSerializable(typeof(object[]))]
+    [JsonSerializable(typeof(string))]
+    [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable))]
+    internal partial class MetadataAndSerializationContext : JsonSerializerContext, ITestContext
+    {
+    }
+
+    public sealed class MetadataAndSerializationContextTests : RealWorldContextTests
+    {
+        public MetadataAndSerializationContextTests() : base(MetadataAndSerializationContext.Default, (options) => new MetadataAndSerializationContext(options)) { }
+
+        [Fact]
+        public override void EnsureFastPathGeneratedAsExpected()
+        {
+            Assert.NotNull(MetadataAndSerializationContext.Default.Location.Serialize);
+            Assert.NotNull(MetadataAndSerializationContext.Default.RepeatedLocation.Serialize);
+            Assert.NotNull(MetadataAndSerializationContext.Default.ActiveOrUpcomingEvent.Serialize);
+            Assert.NotNull(MetadataAndSerializationContext.Default.CampaignSummaryViewModel.Serialize);
+            Assert.NotNull(MetadataAndSerializationContext.Default.IndexViewModel.Serialize);
+            Assert.NotNull(MetadataAndSerializationContext.Default.WeatherForecastWithPOCOs.Serialize);
+            Assert.NotNull(MetadataAndSerializationContext.Default.EmptyPoco.Serialize);
+            Assert.NotNull(MetadataAndSerializationContext.Default.HighLowTemps.Serialize);
+            Assert.NotNull(MetadataAndSerializationContext.Default.MyType.Serialize);
+            Assert.NotNull(MetadataAndSerializationContext.Default.MyType2.Serialize);
+            Assert.NotNull(MetadataAndSerializationContext.Default.MyIntermediateType.Serialize);
+            Assert.NotNull(MetadataAndSerializationContext.Default.HighLowTempsImmutable.Serialize);
+            Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedClass.Serialize);
+            Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedNestedClass.Serialize);
+            Assert.Null(MetadataAndSerializationContext.Default.ObjectArray.Serialize);
+            Assert.Null(MetadataAndSerializationContext.Default.String.Serialize);
+            Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithEnumAndNullable.Serialize);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs
new file mode 100644 (file)
index 0000000..5d4b469
--- /dev/null
@@ -0,0 +1,56 @@
+// 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;
+using Xunit;
+
+namespace System.Text.Json.SourceGeneration.Tests
+{
+    [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation", GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    internal partial class MetadataContext : JsonSerializerContext, ITestContext
+    {
+    }
+
+    public sealed class MetadataContextTests : RealWorldContextTests
+    {
+        public MetadataContextTests() : base(MetadataContext.Default, (options) => new MetadataContext(options)) { }
+
+        [Fact]
+        public override void EnsureFastPathGeneratedAsExpected()
+        {
+            Assert.Null(MetadataContext.Default.Location.Serialize);
+            Assert.Null(MetadataContext.Default.RepeatedLocation.Serialize);
+            Assert.Null(MetadataContext.Default.ActiveOrUpcomingEvent.Serialize);
+            Assert.Null(MetadataContext.Default.CampaignSummaryViewModel.Serialize);
+            Assert.Null(MetadataContext.Default.IndexViewModel.Serialize);
+            Assert.Null(MetadataContext.Default.WeatherForecastWithPOCOs.Serialize);
+            Assert.Null(MetadataContext.Default.EmptyPoco.Serialize);
+            Assert.Null(MetadataContext.Default.HighLowTemps.Serialize);
+            Assert.Null(MetadataContext.Default.MyType.Serialize);
+            Assert.Null(MetadataContext.Default.MyType2.Serialize);
+            Assert.Null(MetadataContext.Default.MyIntermediateType.Serialize);
+            Assert.Null(MetadataContext.Default.HighLowTempsImmutable.Serialize);
+            Assert.Null(MetadataContext.Default.MyNestedClass.Serialize);
+            Assert.Null(MetadataContext.Default.MyNestedNestedClass.Serialize);
+            Assert.Null(MetadataContext.Default.ObjectArray.Serialize);
+            Assert.Null(MetadataContext.Default.String.Serialize);
+            Assert.Null(MetadataContext.Default.ClassWithEnumAndNullable.Serialize);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs
new file mode 100644 (file)
index 0000000..a92759a
--- /dev/null
@@ -0,0 +1,169 @@
+// 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;
+using Xunit;
+
+namespace System.Text.Json.SourceGeneration.Tests
+{
+    [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation", GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)]
+    [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)]
+    [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)]
+    [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Metadata)]
+    [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)]
+    [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)]
+    internal partial class MixedModeContext : JsonSerializerContext, ITestContext
+    {
+    }
+
+    public sealed class MixedModeContextTests : RealWorldContextTests
+    {
+        public MixedModeContextTests() : base(MixedModeContext.Default, (options) => new MixedModeContext(options)) { }
+
+        [Fact]
+        public override void EnsureFastPathGeneratedAsExpected()
+        {
+            Assert.Null(MixedModeContext.Default.Location.Serialize);
+            Assert.NotNull(MixedModeContext.Default.RepeatedLocation.Serialize);
+            Assert.NotNull(MixedModeContext.Default.CampaignSummaryViewModel.Serialize);
+            Assert.Null(MixedModeContext.Default.IndexViewModel.Serialize);
+            Assert.Null(MixedModeContext.Default.WeatherForecastWithPOCOs.Serialize);
+            Assert.NotNull(MixedModeContext.Default.EmptyPoco.Serialize);
+            Assert.NotNull(MixedModeContext.Default.HighLowTemps.Serialize);
+            Assert.NotNull(MixedModeContext.Default.MyType.Serialize);
+            Assert.NotNull(MixedModeContext.Default.MyType2.Serialize);
+            Assert.NotNull(MixedModeContext.Default.MyIntermediateType.Serialize);
+            Assert.Null(MixedModeContext.Default.HighLowTempsImmutable.Serialize);
+            Assert.NotNull(MixedModeContext.Default.MyNestedClass.Serialize);
+            Assert.NotNull(MixedModeContext.Default.MyNestedNestedClass.Serialize);
+            Assert.Null(MixedModeContext.Default.ObjectArray.Serialize);
+            Assert.Null(MixedModeContext.Default.String.Serialize);
+            Assert.NotNull(MixedModeContext.Default.ClassWithEnumAndNullable.Serialize);
+        }
+
+        [Fact]
+        public override void RoundTripIndexViewModel()
+        {
+            IndexViewModel expected = CreateIndexViewModel();
+
+            string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel);
+            JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel), typeof(CampaignSummaryViewModel));
+
+            IndexViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).IndexViewModel);
+            VerifyIndexViewModel(expected, obj);
+        }
+
+        [Fact]
+        public override void RoundTripCampaignSummaryViewModel()
+        {
+            CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel();
+
+            string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel);
+            JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel), typeof(CampaignSummaryViewModel));
+
+            CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel);
+            VerifyCampaignSummaryViewModel(expected, obj);
+
+            AssertFastPathLogicCorrect(json, obj, DefaultContext.CampaignSummaryViewModel);
+        }
+
+        [Fact]
+        public override void RoundTripCollectionsDictionary()
+        {
+            WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs();
+
+            string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs);
+            JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs), typeof(HighLowTemps));
+
+            WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).WeatherForecastWithPOCOs);
+            VerifyWeatherForecastWithPOCOs(expected, obj);
+        }
+
+        [Fact]
+        public override void RoundTripEmptyPoco()
+        {
+            EmptyPoco expected = CreateEmptyPoco();
+
+            string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco);
+            JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco), typeof(EmptyPoco));
+
+            EmptyPoco obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).EmptyPoco);
+            VerifyEmptyPoco(expected, obj);
+
+            AssertFastPathLogicCorrect(json, obj, DefaultContext.EmptyPoco);
+        }
+
+        [Fact]
+        public override void RoundTripTypeNameClash()
+        {
+            RepeatedTypes.Location expected = CreateRepeatedLocation();
+
+            string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation);
+            JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation), typeof(RepeatedTypes.Location));
+
+            RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).RepeatedLocation);
+            VerifyRepeatedLocation(expected, obj);
+
+            AssertFastPathLogicCorrect(json, obj, DefaultContext.RepeatedLocation);
+        }
+
+        [Fact]
+        public override void HandlesNestedTypes()
+        {
+            string json = @"{""MyInt"":5}";
+            MyNestedClass obj = JsonSerializer.Deserialize<MyNestedClass>(json, ((ITestContext)MetadataContext.Default).MyNestedClass);
+            Assert.Equal(5, obj.MyInt);
+            Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass));
+
+            MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize<MyNestedClass.MyNestedNestedClass>(json, ((ITestContext)MetadataContext.Default).MyNestedNestedClass);
+            Assert.Equal(5, obj2.MyInt);
+            Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass));
+        }
+
+        [Fact]
+        public override void SerializeObjectArray()
+        {
+            IndexViewModel index = CreateIndexViewModel();
+            CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel();
+
+            string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray);
+            object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray);
+
+            JsonElement indexAsJsonElement = (JsonElement)arr[0];
+            JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1];
+            VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).IndexViewModel));
+            VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel));
+        }
+
+        [Fact]
+        public override void SerializeObjectArray_WithCustomOptions()
+        {
+            IndexViewModel index = CreateIndexViewModel();
+            CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel();
+
+            ITestContext context = SerializationContextWithCamelCase.Default;
+            Assert.Same(JsonNamingPolicy.CamelCase, ((JsonSerializerContext)context).Options.PropertyNamingPolicy);
+
+            string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray);
+            object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray);
+
+            JsonElement indexAsJsonElement = (JsonElement)arr[0];
+            JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1];
+
+            ITestContext metadataContext = new MetadataContext(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
+            VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), metadataContext.IndexViewModel));
+            VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), metadataContext.CampaignSummaryViewModel));
+        }
+    }
+}
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Reflection;
 using System.Text.Json.Serialization;
 using System.Text.Json.Serialization.Metadata;
-using System.Text.Json.SourceGeneration.Tests;
-using System.Text.Json.SourceGeneration.Tests.JsonSourceGeneration;
 using Xunit;
 
-[assembly: JsonSerializable(typeof(JsonSerializerSourceGeneratorTests.MyNestedClass))]
-[assembly: JsonSerializable(typeof(JsonSerializerSourceGeneratorTests.MyNestedClass.MyNestedNestedClass))]
-[assembly: JsonSerializable(typeof(object[]))]
-[assembly: JsonSerializable(typeof(string))]
-[assembly: JsonSerializable(typeof(JsonSerializerSourceGeneratorTests.ClassWithEnumAndNullable))]
-
 namespace System.Text.Json.SourceGeneration.Tests
 {
-    public static class JsonSerializerSourceGeneratorTests
+    public abstract class RealWorldContextTests
     {
+        protected ITestContext DefaultContext { get; }
+        private Func<JsonSerializerOptions, ITestContext> _contextCreator;
+
+        public RealWorldContextTests(ITestContext defaultContext, Func<JsonSerializerOptions, ITestContext> contextCreator)
+        {
+            DefaultContext = defaultContext;
+            _contextCreator = contextCreator;
+        }
+
+        public abstract void EnsureFastPathGeneratedAsExpected();
+
         [Fact]
-        public static void RoundTripLocation()
+        public virtual void RoundTripLocation()
         {
             Location expected = CreateLocation();
 
-            string json = JsonSerializer.Serialize(expected, JsonContext.Default.Location);
-            Location obj = JsonSerializer.Deserialize(json, JsonContext.Default.Location);
+            string json = JsonSerializer.Serialize(expected, DefaultContext.Location);
+            Location obj = JsonSerializer.Deserialize(json, DefaultContext.Location);
             VerifyLocation(expected, obj);
         }
 
         [Fact]
-        public static void RoundTripIndexViewModel()
+        public virtual void RoundTripIndexViewModel()
         {
             IndexViewModel expected = CreateIndexViewModel();
 
-            string json = JsonSerializer.Serialize(expected, JsonContext.Default.IndexViewModel);
-            IndexViewModel obj = JsonSerializer.Deserialize(json, JsonContext.Default.IndexViewModel);
+            string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel);
+            IndexViewModel obj = JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel);
 
             VerifyIndexViewModel(expected, obj);
         }
 
         [Fact]
-        public static void RoundTripCampaignSummaryViewModel()
+        public virtual void RoundTripCampaignSummaryViewModel()
         {
             CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel();
 
-            string json = JsonSerializer.Serialize(expected, JsonContext.Default.CampaignSummaryViewModel);
-            CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, JsonContext.Default.CampaignSummaryViewModel);
+            string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel);
+            CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel);
 
             VerifyCampaignSummaryViewModel(expected, obj);
         }
 
         [Fact]
-        public static void RoundTripActiveOrUpcomingEvent()
+        public virtual void RoundTripActiveOrUpcomingEvent()
         {
             ActiveOrUpcomingEvent expected = CreateActiveOrUpcomingEvent();
 
-            string json = JsonSerializer.Serialize(expected, JsonContext.Default.ActiveOrUpcomingEvent);
-            ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, JsonContext.Default.ActiveOrUpcomingEvent);
+            string json = JsonSerializer.Serialize(expected, DefaultContext.ActiveOrUpcomingEvent);
+            ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, DefaultContext.ActiveOrUpcomingEvent);
 
             VerifyActiveOrUpcomingEvent(expected, obj);
         }
 
         [Fact]
-        public static void RoundTripCollectionsDictionary()
+        public virtual void RoundTripCollectionsDictionary()
         {
             WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs();
 
-            string json = JsonSerializer.Serialize(expected, JsonContext.Default.WeatherForecastWithPOCOs);
-            WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, JsonContext.Default.WeatherForecastWithPOCOs);
+            string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs);
+            WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs);
 
             VerifyWeatherForecastWithPOCOs(expected, obj);
         }
 
         [Fact]
-        public static void RoundTripEmptyPoco()
+        public virtual void RoundTripEmptyPoco()
         {
             EmptyPoco expected = CreateEmptyPoco();
 
-            string json = JsonSerializer.Serialize(expected, JsonContext.Default.EmptyPoco);
-            EmptyPoco obj = JsonSerializer.Deserialize(json, JsonContext.Default.EmptyPoco);
+            string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco);
+            EmptyPoco obj = JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco);
 
             VerifyEmptyPoco(expected, obj);
         }
 
         [Fact]
-        public static void RoundTripTypeNameClash()
+        public virtual void RoundTripTypeNameClash()
         {
             RepeatedTypes.Location expected = CreateRepeatedLocation();
 
-            string json = JsonSerializer.Serialize(expected, JsonContext.Default.RepeatedLocation);
-            RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, JsonContext.Default.RepeatedLocation);
+            string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation);
+            RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation);
 
             VerifyRepeatedLocation(expected, obj);
         }
 
-        private static Location CreateLocation()
+        protected static Location CreateLocation()
         {
             return new Location
             {
@@ -112,7 +116,7 @@ namespace System.Text.Json.SourceGeneration.Tests
             };
         }
 
-        private static void VerifyLocation(Location expected, Location obj)
+        protected static void VerifyLocation(Location expected, Location obj)
         {
             Assert.Equal(expected.Address1, obj.Address1);
             Assert.Equal(expected.Address2, obj.Address2);
@@ -124,7 +128,7 @@ namespace System.Text.Json.SourceGeneration.Tests
             Assert.Equal(expected.Country, obj.Country);
         }
 
-        private static ActiveOrUpcomingEvent CreateActiveOrUpcomingEvent()
+        protected static ActiveOrUpcomingEvent CreateActiveOrUpcomingEvent()
         {
             return new ActiveOrUpcomingEvent
             {
@@ -139,7 +143,7 @@ namespace System.Text.Json.SourceGeneration.Tests
             };
         }
 
-        private static void VerifyActiveOrUpcomingEvent(ActiveOrUpcomingEvent expected, ActiveOrUpcomingEvent obj)
+        protected static void VerifyActiveOrUpcomingEvent(ActiveOrUpcomingEvent expected, ActiveOrUpcomingEvent obj)
         {
             Assert.Equal(expected.CampaignManagedOrganizerName, obj.CampaignManagedOrganizerName);
             Assert.Equal(expected.CampaignName, obj.CampaignName);
@@ -151,7 +155,7 @@ namespace System.Text.Json.SourceGeneration.Tests
             Assert.Equal(expected.StartDate, obj.StartDate);
         }
 
-        private static CampaignSummaryViewModel CreateCampaignSummaryViewModel()
+        protected static CampaignSummaryViewModel CreateCampaignSummaryViewModel()
         {
             return new CampaignSummaryViewModel
             {
@@ -164,7 +168,7 @@ namespace System.Text.Json.SourceGeneration.Tests
             };
         }
 
-        private static void VerifyCampaignSummaryViewModel(CampaignSummaryViewModel expected, CampaignSummaryViewModel obj)
+        protected static void VerifyCampaignSummaryViewModel(CampaignSummaryViewModel expected, CampaignSummaryViewModel obj)
         {
             Assert.Equal(expected.Description, obj.Description);
             Assert.Equal(expected.Headline, obj.Headline);
@@ -174,7 +178,7 @@ namespace System.Text.Json.SourceGeneration.Tests
             Assert.Equal(expected.Title, obj.Title);
         }
 
-        private static IndexViewModel CreateIndexViewModel()
+        protected static IndexViewModel CreateIndexViewModel()
         {
             return new IndexViewModel
             {
@@ -204,7 +208,7 @@ namespace System.Text.Json.SourceGeneration.Tests
             };
         }
 
-        private static void VerifyIndexViewModel(IndexViewModel expected, IndexViewModel obj)
+        protected static void VerifyIndexViewModel(IndexViewModel expected, IndexViewModel obj)
         {
             Assert.Equal(expected.ActiveOrUpcomingEvents.Count, obj.ActiveOrUpcomingEvents.Count);
             for (int i = 0; i < expected.ActiveOrUpcomingEvents.Count; i++)
@@ -217,7 +221,7 @@ namespace System.Text.Json.SourceGeneration.Tests
             Assert.Equal(expected.IsNewAccount, obj.IsNewAccount);
         }
 
-        private static WeatherForecastWithPOCOs CreateWeatherForecastWithPOCOs()
+        protected static WeatherForecastWithPOCOs CreateWeatherForecastWithPOCOs()
         {
             return new WeatherForecastWithPOCOs
             {
@@ -251,7 +255,7 @@ namespace System.Text.Json.SourceGeneration.Tests
             };
         }
 
-        private static void VerifyWeatherForecastWithPOCOs(WeatherForecastWithPOCOs expected, WeatherForecastWithPOCOs obj)
+        protected static void VerifyWeatherForecastWithPOCOs(WeatherForecastWithPOCOs expected, WeatherForecastWithPOCOs obj)
         {
             Assert.Equal(expected.Date, obj.Date);
             Assert.Equal(expected.TemperatureCelsius, obj.TemperatureCelsius);
@@ -277,7 +281,7 @@ namespace System.Text.Json.SourceGeneration.Tests
             }
         }
 
-        private static RepeatedTypes.Location CreateRepeatedLocation()
+        protected static RepeatedTypes.Location CreateRepeatedLocation()
         {
             return new RepeatedTypes.Location
             {
@@ -293,7 +297,7 @@ namespace System.Text.Json.SourceGeneration.Tests
             };
         }
 
-        private static void VerifyRepeatedLocation(RepeatedTypes.Location expected, RepeatedTypes.Location obj)
+        protected static void VerifyRepeatedLocation(RepeatedTypes.Location expected, RepeatedTypes.Location obj)
         {
             Assert.Equal(expected.FakeAddress1, obj.FakeAddress1);
             Assert.Equal(expected.FakeAddress2, obj.FakeAddress2);
@@ -305,51 +309,51 @@ namespace System.Text.Json.SourceGeneration.Tests
             Assert.Equal(expected.FakeCountry, obj.FakeCountry);
         }
 
-        private static EmptyPoco CreateEmptyPoco() => new EmptyPoco();
+        protected static EmptyPoco CreateEmptyPoco() => new EmptyPoco();
 
-        private static void VerifyEmptyPoco(EmptyPoco expected, EmptyPoco obj)
+        protected static void VerifyEmptyPoco(EmptyPoco expected, EmptyPoco obj)
         {
             Assert.NotNull(expected);
             Assert.NotNull(obj);
         }
 
         [Fact]
-        public static void NestedSameTypeWorks()
+        public virtual void NestedSameTypeWorks()
         {
             MyType myType = new() { Type = new() };
-            string json = JsonSerializer.Serialize(myType, JsonContext.Default.MyType);
-            myType = JsonSerializer.Deserialize(json, JsonContext.Default.MyType);
-            Assert.Equal(json, JsonSerializer.Serialize(myType, JsonContext.Default.MyType));
+            string json = JsonSerializer.Serialize(myType, DefaultContext.MyType);
+            myType = JsonSerializer.Deserialize(json, DefaultContext.MyType);
+            Assert.Equal(json, JsonSerializer.Serialize(myType, DefaultContext.MyType));
 
             MyType2 myType2 = new() { Type = new MyIntermediateType() { Type = myType } };
-            json = JsonSerializer.Serialize(myType2, JsonContext.Default.MyType2);
-            myType2 = JsonSerializer.Deserialize(json, JsonContext.Default.MyType2);
-            Assert.Equal(json, JsonSerializer.Serialize(myType2, JsonContext.Default.MyType2));
+            json = JsonSerializer.Serialize(myType2, DefaultContext.MyType2);
+            myType2 = JsonSerializer.Deserialize(json, DefaultContext.MyType2);
+            Assert.Equal(json, JsonSerializer.Serialize(myType2, DefaultContext.MyType2));
         }
 
         [Fact]
-        public static void SerializeObjectArray()
+        public virtual void SerializeObjectArray()
         {
             IndexViewModel index = CreateIndexViewModel();
             CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel();
 
-            string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, JsonContext.Default.ObjectArray);
-            object[] arr = JsonSerializer.Deserialize(json, JsonContext.Default.ObjectArray);
+            string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray);
+            object[] arr = JsonSerializer.Deserialize(json, DefaultContext.ObjectArray);
 
             JsonElement indexAsJsonElement = (JsonElement)arr[0];
             JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1];
-            VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), JsonContext.Default.IndexViewModel));
-            VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), JsonContext.Default.CampaignSummaryViewModel));
+            VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), DefaultContext.IndexViewModel));
+            VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), DefaultContext.CampaignSummaryViewModel));
         }
 
         [Fact]
-        public static void SerializeObjectArray_WithCustomOptions()
+        public virtual void SerializeObjectArray_WithCustomOptions()
         {
             IndexViewModel index = CreateIndexViewModel();
             CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel();
 
             JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
-            JsonContext context = new(options);
+            ITestContext context = _contextCreator(options);
 
             string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray);
             object[] arr = JsonSerializer.Deserialize(json, context.ObjectArray);
@@ -361,13 +365,13 @@ namespace System.Text.Json.SourceGeneration.Tests
         }
 
         [Fact]
-        public static void SerializeObjectArray_SimpleTypes_WithCustomOptions()
+        public virtual void SerializeObjectArray_SimpleTypes_WithCustomOptions()
         {
             JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
-            JsonContext context = new JsonContext(options);
+            ITestContext context = _contextCreator(options);
 
-            string json = JsonSerializer.Serialize(new object[] { "Hello", "World" }, typeof(object[]), context);
-            object[] arr = (object[])JsonSerializer.Deserialize(json, typeof(object[]), context);
+            string json = JsonSerializer.Serialize(new object[] { "Hello", "World" }, typeof(object[]), (JsonSerializerContext)context);
+            object[] arr = (object[])JsonSerializer.Deserialize(json, typeof(object[]), (JsonSerializerContext)context);
 
             JsonElement hello = (JsonElement)arr[0];
             JsonElement world = (JsonElement)arr[1];
@@ -376,16 +380,16 @@ namespace System.Text.Json.SourceGeneration.Tests
         }
 
         [Fact]
-        public static void HandlesNestedTypes()
+        public virtual void HandlesNestedTypes()
         {
             string json = @"{""MyInt"":5}";
-            MyNestedClass obj = JsonSerializer.Deserialize<MyNestedClass>(json, JsonContext.Default.MyNestedClass);
+            MyNestedClass obj = JsonSerializer.Deserialize<MyNestedClass>(json, DefaultContext.MyNestedClass);
             Assert.Equal(5, obj.MyInt);
-            Assert.Equal(json, JsonSerializer.Serialize(obj, JsonContext.Default.MyNestedClass));
+            Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass));
 
-            MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize<MyNestedClass.MyNestedNestedClass>(json, JsonContext.Default.MyNestedNestedClass);
+            MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize<MyNestedClass.MyNestedNestedClass>(json, DefaultContext.MyNestedNestedClass);
             Assert.Equal(5, obj2.MyInt);
-            Assert.Equal(json, JsonSerializer.Serialize(obj2, JsonContext.Default.MyNestedNestedClass));
+            Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass));
         }
 
         public class MyNestedClass
@@ -399,7 +403,7 @@ namespace System.Text.Json.SourceGeneration.Tests
         }
 
         [Fact]
-        public static void ConstructingFromOptionsKeepsReference()
+        public void ConstructingFromOptionsKeepsReference()
         {
             JsonStringEnumConverter converter = new();
             JsonSerializerOptions options = new()
@@ -408,29 +412,29 @@ namespace System.Text.Json.SourceGeneration.Tests
                 Converters = { converter }
             };
 
-            JsonContext context = new(options);
+            JsonSerializerContext context = (JsonSerializerContext)_contextCreator(options);
             Assert.Same(options, context.Options);
             Assert.Equal(options.PropertyNameCaseInsensitive, context.Options.PropertyNameCaseInsensitive);
             Assert.Same(converter, context.Options.Converters[0]);
         }
 
         [Fact]
-        public static void JsonContextDefaultClonesDefaultOptions()
+        public void JsonContextDefaultClonesDefaultOptions()
         {
-            JsonContext context = JsonContext.Default;
+            JsonSerializerContext context = (JsonSerializerContext)DefaultContext;
             Assert.Equal(0, context.Options.Converters.Count);
         }
 
         [Fact]
-        public static void JsonContextOptionsNotMutableAfterConstruction()
+        public void JsonContextOptionsNotMutableAfterConstruction()
         {
-            JsonContext context = JsonContext.Default;
+            JsonSerializerContext context = (JsonSerializerContext)DefaultContext;
             InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() => context.Options.PropertyNameCaseInsensitive = true);
             string exAsStr = ex.ToString();
             Assert.Contains("JsonSerializerOptions", exAsStr);
             Assert.Contains("JsonSerializerContext", exAsStr);
 
-            context = new JsonContext(new JsonSerializerOptions());
+            context = (JsonSerializerContext)_contextCreator(new JsonSerializerOptions());
             ex = Assert.Throws<InvalidOperationException>(() => context.Options.PropertyNameCaseInsensitive = true);
             exAsStr = ex.ToString();
             Assert.Contains("JsonSerializerOptions", exAsStr);
@@ -438,26 +442,26 @@ namespace System.Text.Json.SourceGeneration.Tests
         }
 
         [Fact]
-        public static void ParameterizedConstructor()
+        public virtual void ParameterizedConstructor()
         {
-            string json = JsonSerializer.Serialize(new HighLowTempsImmutable(1, 2), JsonContext.Default.HighLowTempsImmutable);
+            string json = JsonSerializer.Serialize(new HighLowTempsImmutable(1, 2), DefaultContext.HighLowTempsImmutable);
             Assert.Contains(@"""High"":1", json);
             Assert.Contains(@"""Low"":2", json);
 
             // Deserialization not supported for now.
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize(json, JsonContext.Default.HighLowTempsImmutable));
+            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsImmutable));
         }
 
         [Fact]
-        public static void EnumAndNullable()
+        public virtual void EnumAndNullable()
         {
             RunTest(new ClassWithEnumAndNullable() { Day = DayOfWeek.Monday, NullableDay = DayOfWeek.Tuesday });
             RunTest(new ClassWithEnumAndNullable());
 
-            static void RunTest(ClassWithEnumAndNullable expected)
+            void RunTest(ClassWithEnumAndNullable expected)
             {
-                string json = JsonSerializer.Serialize(expected, JsonContext.Default.ClassWithEnumAndNullable);
-                ClassWithEnumAndNullable actual = JsonSerializer.Deserialize(json, JsonContext.Default.ClassWithEnumAndNullable);
+                string json = JsonSerializer.Serialize(expected, DefaultContext.ClassWithEnumAndNullable);
+                ClassWithEnumAndNullable actual = JsonSerializer.Deserialize(json, DefaultContext.ClassWithEnumAndNullable);
                 Assert.Equal(expected.Day, actual.Day);
                 Assert.Equal(expected.NullableDay, actual.NullableDay);
             }
@@ -470,13 +474,13 @@ namespace System.Text.Json.SourceGeneration.Tests
         }
 
         [Fact]
-        public static void Converters_AndTypeInfoCreator_NotRooted_WhenMetadataNotPresent()
+        public void Converters_AndTypeInfoCreator_NotRooted_WhenMetadataNotPresent()
         {
             object[] objArr = new object[] { new MyStruct() };
 
             // Metadata not generated for MyStruct without JsonSerializableAttribute.
             NotSupportedException ex = Assert.Throws<NotSupportedException>(
-                () => JsonSerializer.Serialize(objArr, JsonContext.Default.ObjectArray));
+                () => JsonSerializer.Serialize(objArr, DefaultContext.ObjectArray));
             string exAsStr = ex.ToString();
             Assert.Contains(typeof(MyStruct).ToString(), exAsStr);
             Assert.Contains("JsonSerializerOptions", exAsStr);
@@ -493,7 +497,7 @@ namespace System.Text.Json.SourceGeneration.Tests
             AssertFieldNull("s_defaultFactoryConverters", optionsInstance: null);
 
             // Confirm type info dynamic creator not set.
-            AssertFieldNull("_typeInfoCreationFunc", JsonContext.Default.Options);
+            AssertFieldNull("_typeInfoCreationFunc", ((JsonSerializerContext)DefaultContext).Options);
 
             static void AssertFieldNull(string fieldName, JsonSerializerOptions? optionsInstance)
             {
@@ -507,7 +511,7 @@ namespace System.Text.Json.SourceGeneration.Tests
         private const string ExceptionMessageFromCustomContext = "Exception thrown from custom context.";
 
         [Fact]
-        public static void GetTypeInfoCalledDuringPolymorphicSerialization()
+        public void GetTypeInfoCalledDuringPolymorphicSerialization()
         {
             CustomContext context = new(new JsonSerializerOptions());
 
@@ -529,13 +533,13 @@ namespace System.Text.Json.SourceGeneration.Tests
 
         internal class CustomContext : JsonSerializerContext
         {
-            public CustomContext(JsonSerializerOptions options) : base(options) { }
+            public CustomContext(JsonSerializerOptions options) : base(options, null) { }
 
             private JsonTypeInfo<object> _object;
             public JsonTypeInfo<object> Object => _object ??= JsonMetadataServices.CreateValueInfo<object>(Options, JsonMetadataServices.ObjectConverter);
 
             private JsonTypeInfo<object[]> _objectArray;
-            public JsonTypeInfo<object[]> ObjectArray => _objectArray ??= JsonMetadataServices.CreateArrayInfo<object>(Options, Object, default);
+            public JsonTypeInfo<object[]> ObjectArray => _objectArray ??= JsonMetadataServices.CreateArrayInfo<object>(Options, Object, default, serializeFunc: null);
 
             public override JsonTypeInfo GetTypeInfo(Type type)
             {
@@ -547,5 +551,15 @@ namespace System.Text.Json.SourceGeneration.Tests
                 throw new InvalidOperationException(ExceptionMessageFromCustomContext);
             }
         }
+
+        protected static void AssertFastPathLogicCorrect<T>(string expectedJson, T value, JsonTypeInfo<T> typeInfo)
+        {
+            using MemoryStream ms = new();
+            using Utf8JsonWriter writer = new(ms);
+            typeInfo.Serialize!(writer, value);
+            writer.Flush();
+
+            JsonTestHelper.AssertJsonEqual(expectedJson, Encoding.UTF8.GetString(ms.ToArray()));
+        }
     }
 }
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs
new file mode 100644 (file)
index 0000000..45ce3f3
--- /dev/null
@@ -0,0 +1,284 @@
+// 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;
+using Xunit;
+
+namespace System.Text.Json.SourceGeneration.Tests
+{
+    [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(RepeatedTypes.Location), GenerationMode = JsonSourceGenerationMode.Serialization, TypeInfoPropertyName = "RepeatedLocation")]
+    [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    internal partial class SerializationContext : JsonSerializerContext, ITestContext
+    {
+    }
+
+    [JsonSerializerOptions(NamingPolicy = JsonKnownNamingPolicy.BuiltInCamelCase)]
+    [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(RepeatedTypes.Location), GenerationMode = JsonSourceGenerationMode.Serialization, TypeInfoPropertyName = "RepeatedLocation")]
+    [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(CampaignSummaryViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(IndexViewModel), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)]
+    internal partial class SerializationContextWithCamelCase : JsonSerializerContext, ITestContext
+    {
+    }
+
+    public sealed class SerializationContextTests : RealWorldContextTests
+    {
+        public SerializationContextTests() : base(SerializationContext.Default, (options) => new SerializationContext(options)) { }
+
+        [Fact]
+        public override void EnsureFastPathGeneratedAsExpected()
+        {
+            Assert.NotNull(SerializationContext.Default.Location.Serialize);
+            Assert.NotNull(SerializationContext.Default.RepeatedLocation.Serialize);
+            Assert.NotNull(SerializationContext.Default.ActiveOrUpcomingEvent.Serialize);
+            Assert.NotNull(SerializationContext.Default.CampaignSummaryViewModel.Serialize);
+            Assert.NotNull(SerializationContext.Default.IndexViewModel.Serialize);
+            Assert.NotNull(SerializationContext.Default.WeatherForecastWithPOCOs.Serialize);
+            Assert.NotNull(SerializationContext.Default.WeatherForecastWithPOCOs.Serialize);
+            Assert.NotNull(SerializationContext.Default.HighLowTemps.Serialize);
+            Assert.NotNull(SerializationContext.Default.MyType.Serialize);
+            Assert.NotNull(SerializationContext.Default.MyType2.Serialize);
+            Assert.NotNull(SerializationContext.Default.MyIntermediateType.Serialize);
+            Assert.NotNull(SerializationContext.Default.HighLowTempsImmutable.Serialize);
+            Assert.NotNull(SerializationContext.Default.MyNestedClass.Serialize);
+            Assert.NotNull(SerializationContext.Default.MyNestedNestedClass.Serialize);
+            Assert.Null(SerializationContext.Default.ObjectArray.Serialize);
+            Assert.Null(SerializationContext.Default.String.Serialize);
+            Assert.NotNull(SerializationContext.Default.ClassWithEnumAndNullable.Serialize);
+        }
+
+        [Fact]
+        public override void RoundTripLocation()
+        {
+            Location expected = CreateLocation();
+
+            string json = JsonSerializer.Serialize(expected, DefaultContext.Location);
+            JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.Location), typeof(Location));
+
+            Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).Location);
+            VerifyLocation(expected, obj);
+
+            AssertFastPathLogicCorrect(json, obj, DefaultContext.Location);
+        }
+
+        [Fact]
+        public override void RoundTripIndexViewModel()
+        {
+            IndexViewModel expected = CreateIndexViewModel();
+
+            string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel);
+            JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel), typeof(IndexViewModel));
+
+            IndexViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).IndexViewModel);
+            VerifyIndexViewModel(expected, obj);
+
+            AssertFastPathLogicCorrect(json, obj, DefaultContext.IndexViewModel);
+        }
+
+        [Fact]
+        public override void RoundTripCampaignSummaryViewModel()
+        {
+            CampaignSummaryViewModel expected = CreateCampaignSummaryViewModel();
+
+            string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel);
+            JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel), typeof(CampaignSummaryViewModel));
+
+            CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel);
+            VerifyCampaignSummaryViewModel(expected, obj);
+
+            AssertFastPathLogicCorrect(json, obj, DefaultContext.CampaignSummaryViewModel);
+        }
+
+        [Fact]
+        public override void RoundTripActiveOrUpcomingEvent()
+        {
+            ActiveOrUpcomingEvent expected = CreateActiveOrUpcomingEvent();
+
+            string json = JsonSerializer.Serialize(expected, DefaultContext.ActiveOrUpcomingEvent);
+            JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.ActiveOrUpcomingEvent), typeof(ActiveOrUpcomingEvent));
+
+            ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ActiveOrUpcomingEvent);
+            VerifyActiveOrUpcomingEvent(expected, obj);
+
+            AssertFastPathLogicCorrect(json, obj, DefaultContext.ActiveOrUpcomingEvent);
+        }
+
+        [Fact]
+        public override void RoundTripCollectionsDictionary()
+        {
+            WeatherForecastWithPOCOs expected = CreateWeatherForecastWithPOCOs();
+
+            string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs);
+            JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs), typeof(WeatherForecastWithPOCOs));
+
+            WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).WeatherForecastWithPOCOs);
+            VerifyWeatherForecastWithPOCOs(expected, obj);
+
+            AssertFastPathLogicCorrect(json, obj, DefaultContext.WeatherForecastWithPOCOs);
+        }
+
+        [Fact]
+        public override void RoundTripEmptyPoco()
+        {
+            EmptyPoco expected = CreateEmptyPoco();
+
+            string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco);
+            JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco), typeof(EmptyPoco));
+
+            EmptyPoco obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).EmptyPoco);
+            VerifyEmptyPoco(expected, obj);
+
+            AssertFastPathLogicCorrect(json, obj, DefaultContext.EmptyPoco);
+        }
+
+        [Fact]
+        public override void RoundTripTypeNameClash()
+        {
+            RepeatedTypes.Location expected = CreateRepeatedLocation();
+
+            string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation);
+            JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation), typeof(RepeatedTypes.Location));
+
+            RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).RepeatedLocation);
+            VerifyRepeatedLocation(expected, obj);
+
+            AssertFastPathLogicCorrect(json, obj, DefaultContext.RepeatedLocation);
+        }
+
+        [Fact]
+        public override void NestedSameTypeWorks()
+        {
+            MyType myType = new() { Type = new() };
+            string json = JsonSerializer.Serialize(myType, DefaultContext.MyType);
+            myType = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyType);
+            AssertFastPathLogicCorrect(json, myType, DefaultContext.MyType);
+
+            MyType2 myType2 = new() { Type = new MyIntermediateType() { Type = myType } };
+            json = JsonSerializer.Serialize(myType2, DefaultContext.MyType2);
+            myType2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyType2);
+            AssertFastPathLogicCorrect(json, myType2, DefaultContext.MyType2);
+        }
+
+        [Fact]
+        public override void SerializeObjectArray()
+        {
+            IndexViewModel index = CreateIndexViewModel();
+            CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel();
+
+            string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray);
+            object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray);
+
+            JsonElement indexAsJsonElement = (JsonElement)arr[0];
+            JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1];
+            VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).IndexViewModel));
+            VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel));
+        }
+
+        [Fact]
+        public override void SerializeObjectArray_WithCustomOptions()
+        {
+            IndexViewModel index = CreateIndexViewModel();
+            CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel();
+
+            ITestContext context = SerializationContextWithCamelCase.Default;
+            Assert.Same(JsonNamingPolicy.CamelCase, ((JsonSerializerContext)context).Options.PropertyNamingPolicy);
+
+            string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray);
+            // Verify JSON was written with camel casing.
+            Assert.Contains("activeOrUpcomingEvents", json);
+            Assert.Contains("featuredCampaign", json);
+            Assert.Contains("description", json);
+            Assert.Contains("organizationName", json);
+
+            object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray);
+
+            JsonElement indexAsJsonElement = (JsonElement)arr[0];
+            JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1];
+
+            ITestContext metadataContext = new MetadataContext(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
+            VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), metadataContext.IndexViewModel));
+            VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), metadataContext.CampaignSummaryViewModel));
+        }
+
+        [Fact]
+        public override void SerializeObjectArray_SimpleTypes_WithCustomOptions()
+        {
+            JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
+            ITestContext context = new SerializationContext(options);
+
+            string json = JsonSerializer.Serialize(new object[] { "Hello", "World" }, typeof(object[]), (JsonSerializerContext)context);
+            object[] arr = (object[])JsonSerializer.Deserialize(json, typeof(object[]), (JsonSerializerContext)((ITestContext)MetadataContext.Default));
+
+            JsonElement hello = (JsonElement)arr[0];
+            JsonElement world = (JsonElement)arr[1];
+            Assert.Equal("\"Hello\"", hello.GetRawText());
+            Assert.Equal("\"World\"", world.GetRawText());
+        }
+
+        [Fact]
+        public override void HandlesNestedTypes()
+        {
+            string json = @"{""MyInt"":5}";
+            MyNestedClass obj = JsonSerializer.Deserialize<MyNestedClass>(json, ((ITestContext)MetadataContext.Default).MyNestedClass);
+            Assert.Equal(5, obj.MyInt);
+            Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass));
+
+            MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize<MyNestedClass.MyNestedNestedClass>(json, ((ITestContext)MetadataContext.Default).MyNestedNestedClass);
+            Assert.Equal(5, obj2.MyInt);
+            Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass));
+        }
+
+        [Fact]
+        public override void EnumAndNullable()
+        {
+            RunTest(new ClassWithEnumAndNullable() { Day = DayOfWeek.Monday, NullableDay = DayOfWeek.Tuesday });
+            RunTest(new ClassWithEnumAndNullable());
+
+            void RunTest(ClassWithEnumAndNullable expected)
+            {
+                string json = JsonSerializer.Serialize(expected, DefaultContext.ClassWithEnumAndNullable);
+                ClassWithEnumAndNullable actual = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ClassWithEnumAndNullable);
+                Assert.Equal(expected.Day, actual.Day);
+                Assert.Equal(expected.NullableDay, actual.NullableDay);
+            }
+        }
+
+        [Fact]
+        public override void ParameterizedConstructor()
+        {
+            string json = JsonSerializer.Serialize(new HighLowTempsImmutable(1, 2), DefaultContext.HighLowTempsImmutable);
+            Assert.Contains(@"""High"":1", json);
+            Assert.Contains(@"""Low"":2", json);
+
+            JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsImmutable), typeof(HighLowTempsImmutable));
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationLogicTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationLogicTests.cs
new file mode 100644 (file)
index 0000000..87d8822
--- /dev/null
@@ -0,0 +1,61 @@
+// 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.Encodings.Web;
+using System.Text.Json.Serialization;
+using Xunit;
+
+namespace System.Text.Json.SourceGeneration.Tests
+{
+    public static class SerializationLogicTests
+    {
+        [Theory]
+        [MemberData(nameof(GetOptionsUsingUnsupportedFeatures))]
+        [MemberData(nameof(GetIncompatibleOptions))]
+        public static void SerializationFuncNotInvokedWhenNotSupported(JsonSerializerOptions options)
+        {
+            JsonMessage message = new();
+
+            // Per context implementation, NotImplementedException thrown because the options are compatible, hence the serialization func is invoked.
+            Assert.Throws<NotImplementedException>(() => JsonSerializer.Serialize(message, JsonContext.Default.JsonMessage));
+            Assert.Throws<NotImplementedException>(() => JsonSerializer.Serialize(message, typeof(JsonMessage), JsonContext.Default));
+
+            // NotSupportedException thrown because
+            // - the options are not compatible, hence the serialization func is not invoked.
+            // - the serializer correctly tries to serialize based on property metadata, but we have not provided it in our implementation.
+            JsonContext context = new(options);
+            JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Serialize(message, context.JsonMessage), typeof(JsonMessage));
+            JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Serialize(message, typeof(JsonMessage), context), typeof(JsonMessage));
+        }
+
+        [Fact]
+        public static void DictionaryFastPathPrimitiveValueSupported()
+        {
+            Assert.NotNull(DictionaryTypeContext.Default.DictionarySystemStringSystemString.Serialize);
+            Assert.NotNull(DictionaryTypeContext.Default.DictionarySystemStringSystemTextJsonSourceGenerationTestsJsonMessage.Serialize);
+            Assert.NotNull(DictionaryTypeContext.Default.JsonMessage.Serialize);
+            Assert.Null(DictionaryTypeContext.Default.String.Serialize);
+            Assert.Null(DictionaryTypeContext.Default.Int32.Serialize);
+        }
+
+        // Options with features that aren't supported in generated serialization funcs.
+        public static IEnumerable<object[]> GetOptionsUsingUnsupportedFeatures()
+        {
+            yield return new object[] { new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } } };
+            yield return new object[] { new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping } };
+            yield return new object[] { new JsonSerializerOptions { NumberHandling = JsonNumberHandling.WriteAsString } };
+            yield return new object[] { new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve } };
+            yield return new object[] { new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.IgnoreCycles } };
+            yield return new object[] { new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.IgnoreCycles } };
+        }
+
+        // Options incompatible with JsonContext.s_defaultOptions below.
+        public static IEnumerable<object[]> GetIncompatibleOptions()
+        {
+            yield return new object[] { new JsonSerializerOptions() };
+            yield return new object[] { new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.Never } };
+            yield return new object[] { new JsonSerializerOptions { IgnoreReadOnlyFields = true } };
+        }
+    }
+}
index 09f2698..a88c018 100644 (file)
@@ -9,8 +9,17 @@
   </ItemGroup>
 
   <ItemGroup>
+    <Compile Include="..\Common\JsonTestHelper.cs" Link="CommonTest\System\Text\Json\JsonTestHelper.cs" />
+    <Compile Include="ContextClasses.cs" />
+    <Compile Include="JsonSerializerContextTests.cs" />
+    <Compile Include="JsonTestHelper.cs" />
+    <Compile Include="MetadataAndSerializationContextTests.cs" />
+    <Compile Include="MetadataContextTests.cs" />
+    <Compile Include="MixedModeContextTests.cs" />
+    <Compile Include="SerializationContextTests.cs" />
+    <Compile Include="SerializationLogicTests.cs" />
     <Compile Include="TestClasses.cs" />
-    <Compile Include="JsonSourceGeneratorTests.cs" />
+    <Compile Include="RealWorldContextTests.cs" />
   </ItemGroup>
 
   <Target Name="FixIncrementalCoreCompileWithAnalyzers" BeforeTargets="CoreCompile">
index d5ebf88..0f1f6c6 100644 (file)
@@ -2,22 +2,6 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Collections.Generic;
-using System.Text.Json.Serialization;
-using System.Text.Json.SourceGeneration.Tests;
-
-[assembly: JsonSerializable(typeof(Location))]
-[assembly: JsonSerializable(typeof(System.Text.Json.SourceGeneration.Tests.RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation")]
-[assembly: JsonSerializable(typeof(ActiveOrUpcomingEvent))]
-[assembly: JsonSerializable(typeof(CampaignSummaryViewModel))]
-[assembly: JsonSerializable(typeof(IndexViewModel))]
-[assembly: JsonSerializable(typeof(WeatherForecastWithPOCOs))]
-[assembly: JsonSerializable(typeof(EmptyPoco))]
-// Ensure no errors when type of member in previously specified object graph is passed as input type to generator.
-[assembly: JsonSerializable(typeof(HighLowTemps))]
-[assembly: JsonSerializable(typeof(MyType))]
-[assembly: JsonSerializable(typeof(MyType2))]
-[assembly: JsonSerializable(typeof(MyIntermediateType))]
-[assembly: JsonSerializable(typeof(HighLowTempsImmutable))]
 
 namespace System.Text.Json.SourceGeneration.Tests.RepeatedTypes
 {
@@ -123,4 +107,10 @@ namespace System.Text.Json.SourceGeneration.Tests
     {
         public MyType Type = new();
     }
+
+    public class JsonMessage
+    {
+        public string Message { get; set; }
+        public int Length => Message?.Length ?? 0; // Read-only property
+    }
 }
index da43706..86a261f 100644 (file)
@@ -7,6 +7,7 @@ using System.IO;
 using System.Linq;
 using System.Reflection;
 using System.Runtime.Serialization;
+using System.Text.Encodings.Web;
 using Microsoft.CodeAnalysis;
 using Microsoft.CodeAnalysis.CSharp;
 using Xunit;
@@ -33,6 +34,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests
                 MetadataReference.CreateFromFile(typeof(Type).Assembly.Location),
                 MetadataReference.CreateFromFile(typeof(KeyValuePair).Assembly.Location),
                 MetadataReference.CreateFromFile(typeof(ContractNamespaceAttribute).Assembly.Location),
+                MetadataReference.CreateFromFile(typeof(JavaScriptEncoder).Assembly.Location),
                 MetadataReference.CreateFromFile(systemRuntimeAssemblyPath),
                 MetadataReference.CreateFromFile(systemCollectionsAssemblyPath),
             };
@@ -167,8 +169,14 @@ namespace System.Text.Json.SourceGeneration.UnitTests
             using System.Collections.Generic;
             using System.Text.Json.Serialization;
 
-            [assembly: JsonSerializable(typeof(Fake.Location))]
-            [assembly: JsonSerializable(typeof(HelloWorld.Location))]
+            namespace JsonSourceGeneration
+            {
+                [JsonSerializable(typeof(Fake.Location))]
+                [JsonSerializable(typeof(HelloWorld.Location))]
+                internal partial class JsonContext : JsonSerializerContext
+                {
+                }
+            }
 
             namespace Fake
             {
index 046c401..caa283f 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.Immutable;
-using System.Linq;
 using Microsoft.CodeAnalysis;
 using Xunit;
 
index f462ca0..654b413 100644 (file)
@@ -17,10 +17,13 @@ namespace System.Text.Json.SourceGeneration.UnitTests
             string source = @"
             using System.Text.Json.Serialization;
 
-            [assembly: JsonSerializable(typeof(HelloWorld.MyType))]
-
             namespace HelloWorld
             {
+                [JsonSerializable(typeof(HelloWorld.MyType))]
+                internal partial class JsonContext : JsonSerializerContext
+                {
+                }
+
                 public class MyType
                 {
                     public int PublicPropertyInt { get; set; }
@@ -83,11 +86,14 @@ namespace System.Text.Json.SourceGeneration.UnitTests
             using System.Text.Json.Serialization;
             using ReferencedAssembly;
 
-            [assembly: JsonSerializable(typeof(HelloWorld.MyType))]
-            [assembly: JsonSerializable(typeof(ReferencedAssembly.Location))]
-
             namespace HelloWorld
             {
+                [JsonSerializable(typeof(HelloWorld.MyType))]
+                [JsonSerializable(typeof(ReferencedAssembly.Location))]
+                internal partial class JsonContext : JsonSerializerContext
+                {
+                }
+
                 public class MyType
                 {
                     public int PublicPropertyInt { get; set; }
@@ -165,15 +171,19 @@ namespace System.Text.Json.SourceGeneration.UnitTests
             using System.Text.Json.Serialization;
             using ReferencedAssembly;
 
-            using @JsonSerializable = System.Runtime.Serialization.ContractNamespaceAttribute;
+            using @JsonSerializable = System.Runtime.Serialization.CollectionDataContractAttribute ;
             using AliasedAttribute = System.Text.Json.Serialization.JsonSerializableAttribute;
 
-            [assembly: AliasedAttribute(typeof(HelloWorld.MyType))]
-            [assembly: AliasedAttribute(typeof(ReferencedAssembly.Location))]
-            [module: @JsonSerializable(""my namespace"")]
-
             namespace HelloWorld
             {
+
+                [AliasedAttribute(typeof(HelloWorld.MyType))]
+                [AliasedAttribute(typeof(ReferencedAssembly.Location))]
+                [@JsonSerializable]
+                internal partial class JsonContext : JsonSerializerContext
+                {
+                }
+
                 public class MyType
                 {
                     public int PublicPropertyInt { get; set; }
@@ -245,12 +255,15 @@ namespace System.Text.Json.SourceGeneration.UnitTests
             string source = @"using System;
 using System.Text.Json.Serialization;
 
-[assembly: JsonSerializable(typeof(int))]
-[assembly: JsonSerializable(typeof(string), TypeInfoPropertyName = ""Str"")]
-
 namespace System.Text.Json.Serialization
 {
-    [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+    [JsonSerializable(typeof(int))]
+    [JsonSerializable(typeof(string), TypeInfoPropertyName = ""Str"")]
+    internal partial class JsonContext : JsonSerializerContext
+    {
+    }
+
+    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
     public sealed class JsonSerializableAttribute : JsonAttribute
     {
         public string TypeInfoPropertyName { get; set; }
@@ -341,11 +354,14 @@ namespace System.Text.Json.Serialization
             using System.Collections.Generic;
             using System.Text.Json.Serialization;
             using ReferencedAssembly;
-
-            [assembly: JsonSerializable(typeof(HelloWorld.WeatherForecastWithPOCOs))]
     
             namespace HelloWorld
             {
+                [JsonSerializable(typeof(HelloWorld.WeatherForecastWithPOCOs))]
+                internal partial class JsonContext : JsonSerializerContext
+                {
+                }
+
                 public class WeatherForecastWithPOCOs
                 {
                     public DateTimeOffset Date { get; set; }
index e08ee5e..afcd88a 100644 (file)
@@ -46,11 +46,14 @@ namespace System.Text.Json.SourceGeneration.UnitTests
             using System.Text.Json.Serialization;
             using ReferencedAssembly;
 
-            [assembly: JsonSerializable(typeof(HelloWorld.MyType))]
-            [assembly: JsonSerializable(typeof(ReferencedAssembly.ReferencedType))]
-
             namespace HelloWorld
             {
+                [JsonSerializable(typeof(HelloWorld.MyType))]
+                [JsonSerializable(typeof(ReferencedAssembly.ReferencedType))]
+                internal partial class JsonContext : JsonSerializerContext
+                {
+                }
+
                 public class MyType
                 {
                     public void MyMethod() { }
@@ -82,10 +85,13 @@ namespace System.Text.Json.SourceGeneration.UnitTests
             using System;
             using System.Text.Json.Serialization;
 
-            [assembly: JsonSerializable(typeof(HelloWorld.MyType))]
-
             namespace HelloWorld
             {
+                [JsonSerializable(typeof(HelloWorld.MyType))]
+                internal partial class JsonContext : JsonSerializerContext
+                {
+                }
+
                 public class MyType
                 {
                     [JsonInclude]
index f7f2aa2..338445a 100644 (file)
@@ -15,7 +15,7 @@ using Xunit.Sdk;
 
 namespace System.Text.Json
 {
-    internal static class JsonTestHelper
+    internal static partial class JsonTestHelper
     {
 #if BUILDING_INBOX_LIBRARY
         public const string DoubleFormatString = null;
@@ -818,64 +818,5 @@ namespace System.Text.Json
             => s_replaceNewlines ?
             value.Replace(CompiledNewline, Environment.NewLine) :
             value;
-
-        public static void AssertJsonEqual(string expected, string actual)
-        {
-            using JsonDocument expectedDom = JsonDocument.Parse(expected);
-            using JsonDocument actualDom = JsonDocument.Parse(actual);
-            AssertJsonEqual(expectedDom.RootElement, actualDom.RootElement);
-        }
-
-        private static void AssertJsonEqual(JsonElement expected, JsonElement actual)
-        {
-            JsonValueKind valueKind = expected.ValueKind;
-            Assert.Equal(valueKind, actual.ValueKind);
-
-            switch (valueKind)
-            {
-                case JsonValueKind.Object:
-                    var propertyNames = new HashSet<string>();
-
-                    foreach (JsonProperty property in expected.EnumerateObject())
-                    {
-                        propertyNames.Add(property.Name);
-                    }
-
-                    foreach (JsonProperty property in actual.EnumerateObject())
-                    {
-                        propertyNames.Add(property.Name);
-                    }
-
-                    foreach (string name in propertyNames)
-                    {
-                        AssertJsonEqual(expected.GetProperty(name), actual.GetProperty(name));
-                    }
-                    break;
-                case JsonValueKind.Array:
-                    JsonElement.ArrayEnumerator expectedEnumerator = actual.EnumerateArray();
-                    JsonElement.ArrayEnumerator actualEnumerator = expected.EnumerateArray();
-
-                    while (expectedEnumerator.MoveNext())
-                    {
-                        Assert.True(actualEnumerator.MoveNext());
-                        AssertJsonEqual(expectedEnumerator.Current, actualEnumerator.Current);
-                    }
-
-                    Assert.False(actualEnumerator.MoveNext());
-                    break;
-                case JsonValueKind.String:
-                    Assert.Equal(expected.GetString(), actual.GetString());
-                    break;
-                case JsonValueKind.Number:
-                case JsonValueKind.True:
-                case JsonValueKind.False:
-                case JsonValueKind.Null:
-                    Assert.Equal(expected.GetRawText(), actual.GetRawText());
-                    break;
-                default:
-                    Debug.Fail($"Unexpected JsonValueKind: JsonValueKind.{valueKind}.");
-                    break;
-            }
-        }
     }
 }
index 781e0fa..b3e3bb3 100644 (file)
@@ -23,7 +23,7 @@ namespace System.Text.Json.Tests.Serialization
                     }
                     else
                     {
-                        _Dictionary = JsonMetadataServices.CreateDictionaryInfo<Dictionary<string, HighLowTemps>, string, HighLowTemps>(Options, () => new Dictionary<string, HighLowTemps>(), this.String, this.HighLowTemps, default);
+                        _Dictionary = JsonMetadataServices.CreateDictionaryInfo<Dictionary<string, HighLowTemps>, string, HighLowTemps>(Options, () => new Dictionary<string, HighLowTemps>(), this.String, this.HighLowTemps, default, serializeFunc: null);
                     }
                 }
 
index c477b08..39899a5 100644 (file)
@@ -22,15 +22,14 @@ namespace System.Text.Json.Tests.Serialization
                     }
                     else
                     {
-                        JsonTypeInfo<HighLowTemps> objectInfo = JsonMetadataServices.CreateObjectInfo<HighLowTemps>();
-                        _HighLowTemps = objectInfo;
-    
-                        JsonMetadataServices.InitializeObjectInfo(
-                            objectInfo,
+                        JsonTypeInfo<HighLowTemps> objectInfo = JsonMetadataServices.CreateObjectInfo<HighLowTemps>(
                             Options,
                             createObjectFunc: static () => new HighLowTemps(),
                             HighLowTempsPropInitFunc,
-                            default);
+                            default,
+                            serializeFunc: null);
+
+                        _HighLowTemps = objectInfo;
                     }
                 }
 
index f317268..c16add2 100644 (file)
@@ -11,11 +11,11 @@ namespace System.Text.Json.Tests.Serialization
         private static JsonContext s_default;
         public static JsonContext Default => s_default ??= new JsonContext(new JsonSerializerOptions());
 
-        public JsonContext() : base(null)
+        public JsonContext() : base(null, null)
         {
         }
 
-        public JsonContext(JsonSerializerOptions options) : base(options)
+        public JsonContext(JsonSerializerOptions options) : base(options, null)
         {
         }
 
index 24a2e95..3a09255 100644 (file)
@@ -23,7 +23,7 @@ namespace System.Text.Json.Tests.Serialization
                     }
                     else
                     {
-                        _ListSystemDateTimeOffset = JsonMetadataServices.CreateListInfo<List<DateTimeOffset>, DateTimeOffset>(Options, () => new List<DateTimeOffset>(), this.DateTimeOffset, default);
+                        _ListSystemDateTimeOffset = JsonMetadataServices.CreateListInfo<List<DateTimeOffset>, DateTimeOffset>(Options, () => new List<DateTimeOffset>(), this.DateTimeOffset, default, serializeFunc: null);
                     }
                 }
 
index 21445e3..6fab57d 100644 (file)
@@ -22,7 +22,7 @@ namespace System.Text.Json.Tests.Serialization
                     }
                     else
                     {
-                        _StringArray = JsonMetadataServices.CreateArrayInfo<string>(Options, this.String, default);
+                        _StringArray = JsonMetadataServices.CreateArrayInfo<string>(Options, this.String, default, serializeFunc: null);
                     }
                 }
 
index 62d8593..f75200b 100644 (file)
@@ -22,15 +22,14 @@ namespace System.Text.Json.Tests.Serialization
                     }
                     else
                     {
-                        JsonTypeInfo<WeatherForecastWithPOCOs> objectInfo = JsonMetadataServices.CreateObjectInfo<WeatherForecastWithPOCOs>();
-                        _WeatherForecastWithPOCOs = objectInfo;
-    
-                        JsonMetadataServices.InitializeObjectInfo(
-                            objectInfo,
+                        JsonTypeInfo<WeatherForecastWithPOCOs> objectInfo = JsonMetadataServices.CreateObjectInfo<WeatherForecastWithPOCOs>(
                             Options,
                             createObjectFunc: static () => new WeatherForecastWithPOCOs(),
                             WeatherForecastWithPOCOsPropInitFunc,
-                            default);
+                            default,
+                            serializeFunc: null);
+
+                        _WeatherForecastWithPOCOs = objectInfo;
                     }
                 }
 
index 40feb1b..227aab7 100644 (file)
@@ -106,43 +106,41 @@ namespace System.Text.Json.Tests.Serialization
         {
             JsonSerializerOptions options = new();
 
-            JsonTypeInfo<MyClass> info = JsonMetadataServices.CreateObjectInfo<MyClass>();
-
-            // Null info
-            ArgumentNullException ane = Assert.Throws<ArgumentNullException>(() => JsonMetadataServices.InitializeObjectInfo<MyClass>(
-                info: null,
-                options: options,
+            // Null options
+            ArgumentNullException ane = Assert.Throws<ArgumentNullException>(() => JsonMetadataServices.CreateObjectInfo<MyClass>(
+                options: null,
                 createObjectFunc: null,
                 propInitFunc: (context) => Array.Empty<JsonPropertyInfo>(),
-                numberHandling: default));
-            Assert.Contains("info", ane.ToString());
+                numberHandling: default,
+                serializeFunc: null));
+            Assert.Contains("options", ane.ToString());
 
-            // Info is not for object converter strategy
-            ArgumentException ae = Assert.Throws<ArgumentException>(() => JsonMetadataServices.InitializeObjectInfo(
-                info: JsonMetadataServices.CreateValueInfo<MyClass>(options, new DerivedClassConverter()),
-                options: options,
+            // Null prop init func is fine if serialize func is provided.
+            JsonMetadataServices.CreateObjectInfo<MyClass>(
+                options,
                 createObjectFunc: null,
-                propInitFunc: (context) => Array.Empty<JsonPropertyInfo>(),
-                numberHandling: default));
-            Assert.Contains("info", ae.ToString());
+                propInitFunc: null,
+                numberHandling: default,
+                serializeFunc: (writer, obj) => { });
 
-            // Null options
-            ane = Assert.Throws<ArgumentNullException>(() => JsonMetadataServices.InitializeObjectInfo(
-                info: info,
-                options: null,
+            // Null serialize func is fine if prop init func is provided.
+            JsonMetadataServices.CreateObjectInfo<MyClass>(
+                options,
                 createObjectFunc: null,
                 propInitFunc: (context) => Array.Empty<JsonPropertyInfo>(),
-                numberHandling: default));
-            Assert.Contains("options", ane.ToString());
+                numberHandling: default,
+                serializeFunc: null);
 
-            // Null prop init func.
-            ane = Assert.Throws<ArgumentNullException>(() => JsonMetadataServices.InitializeObjectInfo(
-                info: info,
-                options: options,
+            // Null prop init func and serialize func
+            InvalidOperationException ioe = Assert.Throws<InvalidOperationException>(() => JsonMetadataServices.CreateObjectInfo<MyClass>(
+                options,
                 createObjectFunc: null,
                 propInitFunc: null,
-                numberHandling: default));
-            Assert.Contains("propInitFunc", ane.ToString());
+                numberHandling: default,
+                serializeFunc: null));
+            string ioeAsStr = ioe.ToString();
+            Assert.Contains("propInitFunc", ioeAsStr);
+            Assert.Contains("serializeFunc", ioeAsStr);
         }
 
         [Fact]
@@ -170,14 +168,16 @@ namespace System.Text.Json.Tests.Serialization
             ArgumentNullException ane = Assert.Throws<ArgumentNullException>(() => JsonMetadataServices.CreateArrayInfo<int>(
                 options: null,
                 elementInfo: JsonMetadataServices.CreateValueInfo<int>(options, JsonMetadataServices.Int32Converter),
-                numberHandling: default));
+                numberHandling: default,
+                serializeFunc: null));
             Assert.Contains("options", ane.ToString());
 
             // Null element info
             ane = Assert.Throws<ArgumentNullException>(() => JsonMetadataServices.CreateArrayInfo<int>(
-                options: options,
+                options,
                 elementInfo: null,
-                numberHandling: default));
+                numberHandling: default,
+                serializeFunc: null));
             Assert.Contains("elementInfo", ane.ToString());
         }
 
@@ -191,7 +191,8 @@ namespace System.Text.Json.Tests.Serialization
                 options: null,
                 createObjectFunc: null,
                 elementInfo: JsonMetadataServices.CreateValueInfo<int>(options, JsonMetadataServices.Int32Converter),
-                numberHandling: default));
+                numberHandling: default,
+                serializeFunc: null));
             Assert.Contains("options", ane.ToString());
 
             // Null element info
@@ -199,7 +200,8 @@ namespace System.Text.Json.Tests.Serialization
                 options: options,
                 createObjectFunc: null,
                 elementInfo: null,
-                numberHandling: default));
+                numberHandling: default,
+                serializeFunc: null));
             Assert.Contains("elementInfo", ane.ToString());
         }
 
@@ -214,7 +216,8 @@ namespace System.Text.Json.Tests.Serialization
                 createObjectFunc: null,
                 keyInfo: JsonMetadataServices.CreateValueInfo<string>(options, JsonMetadataServices.StringConverter),
                 valueInfo: JsonMetadataServices.CreateValueInfo<int>(options, JsonMetadataServices.Int32Converter),
-                numberHandling: default));
+                numberHandling: default,
+                serializeFunc: null));
             Assert.Contains("options", ane.ToString());
 
             // Null key info
@@ -223,7 +226,8 @@ namespace System.Text.Json.Tests.Serialization
                 createObjectFunc: null,
                 keyInfo: null,
                 valueInfo: JsonMetadataServices.CreateValueInfo<int>(options, JsonMetadataServices.Int32Converter),
-                numberHandling: default));
+                numberHandling: default,
+                serializeFunc: null));
             Assert.Contains("keyInfo", ane.ToString());
 
             // Null value info
@@ -232,7 +236,8 @@ namespace System.Text.Json.Tests.Serialization
                 createObjectFunc: null,
                 keyInfo: JsonMetadataServices.CreateValueInfo<string>(options, JsonMetadataServices.StringConverter),
                 valueInfo: null,
-                numberHandling: default));
+                numberHandling: default,
+                serializeFunc: null));
             Assert.Contains("valueInfo", ane.ToString());
         }
 
index 285eb39..7ebb217 100644 (file)
@@ -89,16 +89,16 @@ namespace System.Text.Json.Tests.Serialization
 
         private class MyJsonContext : JsonSerializerContext
         {
-            public MyJsonContext() : base(null) { }
+            public MyJsonContext() : base(null, null) { }
 
-            public MyJsonContext(JsonSerializerOptions options) : base(options) { }
+            public MyJsonContext(JsonSerializerOptions options) : base(options, null) { }
 
             public override JsonTypeInfo? GetTypeInfo(Type type) => throw new NotImplementedException();
         }
 
         private class MyJsonContextThatSetsOptionsInParameterlessCtor : JsonSerializerContext
         {
-            public MyJsonContextThatSetsOptionsInParameterlessCtor() : base(new JsonSerializerOptions()) { }
+            public MyJsonContextThatSetsOptionsInParameterlessCtor() : base(new JsonSerializerOptions(), null) { }
             public override JsonTypeInfo? GetTypeInfo(Type type) => throw new NotImplementedException();
         }
     }
index 62488ff..b4773ff 100644 (file)
@@ -15,6 +15,7 @@
   </PropertyGroup>
   <ItemGroup>
     <Compile Include="$(CommonTestPath)System\IO\WrappedMemoryStream.cs" Link="CommonTest\System\IO\WrappedMemoryStream.cs" />
+    <Compile Include="..\Common\JsonTestHelper.cs" Link="CommonTest\System\Text\Json\JsonTestHelper.cs" />
     <Compile Include="BitStackTests.cs" />
     <Compile Include="BufferFactory.cs" />
     <Compile Include="BufferSegment.cs" />