diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonIgnoreCondition.cs b/src/libraries/System.Text.Json/Common/JsonIgnoreCondition.cs
similarity index 87%
rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonIgnoreCondition.cs
rename to src/libraries/System.Text.Json/Common/JsonIgnoreCondition.cs
index f54b45f97cbf9a..5f2595961a868f 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonIgnoreCondition.cs
+++ b/src/libraries/System.Text.Json/Common/JsonIgnoreCondition.cs
@@ -7,10 +7,15 @@ namespace System.Text.Json.Serialization
/// When specified on ,
/// determines when properties and fields across the type graph are ignored.
/// When specified on , controls whether
- /// a property is ignored during serialization and deserialization. This option
+ /// a property or field is ignored during serialization and deserialization. This option
/// overrides the setting on .
///
- public enum JsonIgnoreCondition
+#if BUILDING_SOURCE_GENERATOR
+ internal
+#else
+ public
+#endif
+ enum JsonIgnoreCondition
{
///
/// Property is never ignored during serialization or deserialization.
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonNumberHandling.cs b/src/libraries/System.Text.Json/Common/JsonNumberHandling.cs
similarity index 94%
rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonNumberHandling.cs
rename to src/libraries/System.Text.Json/Common/JsonNumberHandling.cs
index c8eabaad5089c7..8dafef22a1982b 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonNumberHandling.cs
+++ b/src/libraries/System.Text.Json/Common/JsonNumberHandling.cs
@@ -7,12 +7,18 @@ namespace System.Text.Json.Serialization
/// Determines how handles numbers when serializing and deserializing.
///
[Flags]
- public enum JsonNumberHandling
+#if BUILDING_SOURCE_GENERATOR
+ internal
+#else
+ public
+#endif
+ enum JsonNumberHandling
{
///
/// Numbers will only be read from tokens and will only be written as JSON numbers (without quotes).
///
Strict = 0x0,
+
///
/// Numbers can be read from tokens.
/// Does not prevent numbers from being read from token.
@@ -20,10 +26,12 @@ public enum JsonNumberHandling
/// Leading or trailing trivia within the string token, including whitespace, is not allowed.
///
AllowReadingFromString = 0x1,
+
///
/// Numbers will be written as JSON strings (with quotes), not as JSON numbers.
///
WriteAsString = 0x2,
+
///
/// The "NaN", "Infinity", and "-Infinity" tokens can be read as
/// floating-point constants, and the and values for these
diff --git a/src/libraries/System.Text.Json/System.Text.Json.sln b/src/libraries/System.Text.Json/System.Text.Json.sln
index cf494364f9866e..433c378f98852a 100644
--- a/src/libraries/System.Text.Json/System.Text.Json.sln
+++ b/src/libraries/System.Text.Json/System.Text.Json.sln
@@ -27,18 +27,25 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json", "ref\Sys
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json", "src\System.Text.Json.csproj", "{1285FF43-F491-4BE0-B92C-37DA689CBD4B}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json.Tests", "tests\System.Text.Json.Tests.csproj", "{607D1960-1428-40D5-8AC4-D98E3C9BCF47}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{3C544454-BD8B-44F4-A174-B61F18957613}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{EC8CE194-261A-4115-9582-E2DB1A25CAFB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{676B6044-FA47-4B7D-AEC2-FA94DB23A423}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{74017ACD-3AC1-4BB5-804B-D57E305FFBD9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration", "gen\System.Text.Json.SourceGeneration.csproj", "{6485EED4-C313-4551-9865-8ADCED603629}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.Tests", "tests\System.Text.Json.Tests\System.Text.Json.Tests.csproj", "{A0178BAA-A1AF-4C69-8E4A-A700A2723DDC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration.Tests", "tests\System.Text.Json.SourceGeneration.Tests\System.Text.Json.SourceGeneration.Tests.csproj", "{33599A6C-F340-4E1B-9B4D-CB8946C22140}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration.UnitTests", "tests\System.Text.Json.SourceGeneration.UnitTests\System.Text.Json.SourceGeneration.UnitTests.csproj", "{18173CEC-895F-4F62-B7BB-B724457FEDCD}"
+EndProject
Global
GlobalSection(NestedProjects) = preSolution
{102945CA-3736-4B2C-8E68-242A0B247F2B} = {3C544454-BD8B-44F4-A174-B61F18957613}
- {607D1960-1428-40D5-8AC4-D98E3C9BCF47} = {3C544454-BD8B-44F4-A174-B61F18957613}
{73D5739C-E382-4E22-A7D3-B82705C58C74} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB}
{25C42754-B384-4842-8FA7-75D7A79ADF0D} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB}
{4774F56D-16A8-4ABB-8C73-5F57609F1773} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB}
@@ -52,6 +59,10 @@ Global
{7909EB27-0D6E-46E6-B9F9-8A1EFD557018} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423}
{9BCCDA15-8907-4AE3-8871-2F17775DDE4C} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423}
{1285FF43-F491-4BE0-B92C-37DA689CBD4B} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423}
+ {6485EED4-C313-4551-9865-8ADCED603629} = {74017ACD-3AC1-4BB5-804B-D57E305FFBD9}
+ {A0178BAA-A1AF-4C69-8E4A-A700A2723DDC} = {3C544454-BD8B-44F4-A174-B61F18957613}
+ {33599A6C-F340-4E1B-9B4D-CB8946C22140} = {3C544454-BD8B-44F4-A174-B61F18957613}
+ {18173CEC-895F-4F62-B7BB-B724457FEDCD} = {3C544454-BD8B-44F4-A174-B61F18957613}
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -114,10 +125,22 @@ Global
{1285FF43-F491-4BE0-B92C-37DA689CBD4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1285FF43-F491-4BE0-B92C-37DA689CBD4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1285FF43-F491-4BE0-B92C-37DA689CBD4B}.Release|Any CPU.Build.0 = Release|Any CPU
- {607D1960-1428-40D5-8AC4-D98E3C9BCF47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {607D1960-1428-40D5-8AC4-D98E3C9BCF47}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {607D1960-1428-40D5-8AC4-D98E3C9BCF47}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {607D1960-1428-40D5-8AC4-D98E3C9BCF47}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6485EED4-C313-4551-9865-8ADCED603629}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6485EED4-C313-4551-9865-8ADCED603629}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6485EED4-C313-4551-9865-8ADCED603629}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6485EED4-C313-4551-9865-8ADCED603629}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A0178BAA-A1AF-4C69-8E4A-A700A2723DDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A0178BAA-A1AF-4C69-8E4A-A700A2723DDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A0178BAA-A1AF-4C69-8E4A-A700A2723DDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A0178BAA-A1AF-4C69-8E4A-A700A2723DDC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {33599A6C-F340-4E1B-9B4D-CB8946C22140}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {33599A6C-F340-4E1B-9B4D-CB8946C22140}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {33599A6C-F340-4E1B-9B4D-CB8946C22140}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {33599A6C-F340-4E1B-9B4D-CB8946C22140}.Release|Any CPU.Build.0 = Release|Any CPU
+ {18173CEC-895F-4F62-B7BB-B724457FEDCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {18173CEC-895F-4F62-B7BB-B724457FEDCD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {18173CEC-895F-4F62-B7BB-B724457FEDCD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {18173CEC-895F-4F62-B7BB-B724457FEDCD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/libraries/System.Text.Json/gen/ClassType.cs b/src/libraries/System.Text.Json/gen/ClassType.cs
new file mode 100644
index 00000000000000..fa6aab3d2573bd
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/ClassType.cs
@@ -0,0 +1,21 @@
+// 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 enum ClassType
+ {
+ TypeUnsupportedBySourceGen = 0,
+ Object = 1,
+ KnownType = 2,
+ TypeWithDesignTimeProvidedCustomConverter = 3,
+ Enumerable = 4,
+ Dictionary = 5,
+ Nullable = 6,
+ Enum = 7,
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/CollectionType.cs b/src/libraries/System.Text.Json/gen/CollectionType.cs
new file mode 100644
index 00000000000000..6f736698f47b8f
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/CollectionType.cs
@@ -0,0 +1,19 @@
+// 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 enum CollectionType
+ {
+ NotApplicable = 0,
+ Array = 1,
+ List = 2,
+ IEnumerable = 3,
+ IList = 4,
+ Dictionary = 5
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/IsExternalInit.cs b/src/libraries/System.Text.Json/gen/IsExternalInit.cs
new file mode 100644
index 00000000000000..d5984b4be38351
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/IsExternalInit.cs
@@ -0,0 +1,10 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Runtime.CompilerServices
+{
+ ///
+ /// Dummy class so C# init-only properties can compile on NetStandard.
+ ///
+ internal sealed class IsExternalInit { }
+}
diff --git a/src/libraries/System.Text.Json/gen/JsonSerializableSyntaxReceiver.cs b/src/libraries/System.Text.Json/gen/JsonSerializableSyntaxReceiver.cs
new file mode 100644
index 00000000000000..f60f4cfb686f9d
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/JsonSerializableSyntaxReceiver.cs
@@ -0,0 +1,22 @@
+// 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 Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace System.Text.Json.SourceGeneration
+{
+ internal sealed class JsonSerializableSyntaxReceiver : ISyntaxReceiver
+ {
+ public List CompilationUnits { get; } = new();
+
+ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
+ {
+ if (syntaxNode is CompilationUnitSyntax compilationUnit)
+ {
+ CompilationUnits.Add(compilationUnit);
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs
new file mode 100644
index 00000000000000..ef2412f6aaf901
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs
@@ -0,0 +1,128 @@
+// 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.Linq;
+using System.Reflection;
+using System.Text.Json.SourceGeneration.Reflection;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace System.Text.Json.SourceGeneration
+{
+ ///
+ /// Generates source code to optimize serialization and deserialization with JsonSerializer.
+ ///
+ [Generator]
+ public sealed class JsonSourceGenerator : ISourceGenerator
+ {
+ private JsonSourceGeneratorHelper? _helper;
+
+ ///
+ /// Helper for unit tests.
+ ///
+ public Dictionary? SerializableTypes => _helper.GetSerializableTypes();
+
+ ///
+ /// Registers a syntax resolver to receive compilation units.
+ ///
+ ///
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ context.RegisterForSyntaxNotifications(() => new JsonSerializableSyntaxReceiver());
+ }
+
+ ///
+ /// Generates source code to optimize serialization and deserialization with JsonSerializer.
+ ///
+ ///
+ public void Execute(GeneratorExecutionContext executionContext)
+ {
+ Compilation compilation = executionContext.Compilation;
+
+ const string JsonSerializableAttributeName = "System.Text.Json.Serialization.JsonSerializableAttribute";
+ INamedTypeSymbol jsonSerializableAttribute = compilation.GetTypeByMetadataName(JsonSerializableAttributeName);
+ if (jsonSerializableAttribute == null)
+ {
+ return;
+ }
+
+ JsonSerializableSyntaxReceiver receiver = (JsonSerializableSyntaxReceiver)executionContext.SyntaxReceiver;
+ MetadataLoadContextInternal metadataLoadContext = new(compilation);
+
+ TypeExtensions.NullableOfTType = metadataLoadContext.Resolve(typeof(Nullable<>));
+
+ JsonSourceGeneratorHelper helper = new(executionContext, metadataLoadContext);
+ _helper = helper;
+
+ // Discover serializable types indicated by JsonSerializableAttribute.
+ foreach (CompilationUnitSyntax compilationUnit in receiver.CompilationUnits)
+ {
+ SemanticModel compilationSemanticModel = executionContext.Compilation.GetSemanticModel(compilationUnit.SyntaxTree);
+
+ foreach (AttributeListSyntax attributeListSyntax in compilationUnit.AttributeLists)
+ {
+ AttributeSyntax attributeSyntax = attributeListSyntax.Attributes.First();
+ IMethodSymbol attributeSymbol = compilationSemanticModel.GetSymbolInfo(attributeSyntax).Symbol as IMethodSymbol;
+
+ if (attributeSymbol == null || !jsonSerializableAttribute.Equals(attributeSymbol.ContainingType, SymbolEqualityComparer.Default))
+ {
+ // Not the right attribute.
+ continue;
+ }
+
+ // Get JsonSerializableAttribute arguments.
+ IEnumerable attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax);
+
+ ITypeSymbol? typeSymbol = null;
+ string? typeInfoPropertyName = null;
+
+ int i = 0;
+ foreach (AttributeArgumentSyntax node in attributeArguments)
+ {
+ if (i == 0)
+ {
+ TypeOfExpressionSyntax? typeNode = node.ChildNodes().Single() as TypeOfExpressionSyntax;
+ if (typeNode != null)
+ {
+ ExpressionSyntax typeNameSyntax = (ExpressionSyntax)typeNode.ChildNodes().Single();
+ typeSymbol = compilationSemanticModel.GetTypeInfo(typeNameSyntax).ConvertedType;
+ }
+ }
+ else if (i == 1)
+ {
+ // Obtain the optional TypeInfoPropertyName string property on the attribute, if present.
+ SyntaxNode? typeInfoPropertyNameNode = node.ChildNodes().ElementAtOrDefault(1);
+ if (typeInfoPropertyNameNode != null)
+ {
+ typeInfoPropertyName = typeInfoPropertyNameNode.GetFirstToken().ValueText;
+ }
+ }
+
+ i++;
+ }
+
+ if (typeSymbol == null)
+ {
+ continue;
+ }
+
+
+ Type type = new TypeWrapper(typeSymbol, metadataLoadContext);
+ if (type.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;
+ }
+
+ helper.RegisterRootSerializableType(type, typeInfoPropertyName);
+ }
+ }
+
+ helper.GenerateSerializationMetadata();
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGeneratorHelper.Generate.cs b/src/libraries/System.Text.Json/gen/JsonSourceGeneratorHelper.Generate.cs
new file mode 100644
index 00000000000000..040c11cf9acf0e
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGeneratorHelper.Generate.cs
@@ -0,0 +1,651 @@
+// 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.Linq;
+using System.Text.Json.Serialization;
+using System.Text.Json.SourceGeneration.Reflection;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+
+namespace System.Text.Json.SourceGeneration
+{
+ internal sealed partial class JsonSourceGeneratorHelper
+ {
+ private readonly string _generationNamespace;
+
+ // TODO: consider public option for this.
+ // Converter-honoring logic generation can be simplified
+ // if we don't plan to have a feature around this.
+ private bool _honorRuntimeProvidedCustomConverters = true;
+
+ 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 CreateValueInfoMethodName = "CreateValueInfo";
+
+ private static DiagnosticDescriptor TypeNotSupported { get; } = new DiagnosticDescriptor(
+ "SYSLIB2021",
+ "Did not generate serialization metadata for type",
+ "Did not generate serialization metadata for type {0}",
+ "JSON source generation",
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true,
+ description: "Error message: {2}");
+
+ private static DiagnosticDescriptor DuplicateTypeName { get; } = new DiagnosticDescriptor(
+ "SYSLIB2022",
+ "Duplicate type name",
+ "There are multiple types named {0}. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.",
+ "JSON source generation",
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true,
+ description: "Error message: {2}");
+
+ ///
+ /// 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.
+ ///
+ private readonly HashSet _typesWithMetadataGenerated = new();
+
+ private void GenerateTypeMetadata(TypeMetadata typeMetadata)
+ {
+ Debug.Assert(typeMetadata != null);
+
+ if (_typesWithMetadataGenerated.Contains(typeMetadata))
+ {
+ return;
+ }
+
+ _typesWithMetadataGenerated.Add(typeMetadata);
+
+ string source;
+
+ switch (typeMetadata.ClassType)
+ {
+ case ClassType.KnownType:
+ {
+ source = GenerateForTypeWithKnownConverter(typeMetadata);
+ }
+ break;
+ case ClassType.TypeWithDesignTimeProvidedCustomConverter:
+ {
+ source = GenerateForTypeWithUnknownConverter(typeMetadata);
+ }
+ break;
+ case ClassType.Nullable:
+ {
+ source = GenerateForNullable(typeMetadata);
+
+ GenerateTypeMetadata(typeMetadata.NullableUnderlyingTypeMetadata);
+ }
+ break;
+ case ClassType.Enum:
+ {
+ source = GenerateForEnum(typeMetadata);
+ }
+ break;
+ case ClassType.Enumerable:
+ {
+ source = GenerateForCollection(typeMetadata);
+
+ GenerateTypeMetadata(typeMetadata.CollectionValueTypeMetadata);
+ }
+ break;
+ case ClassType.Dictionary:
+ {
+ source = GenerateForCollection(typeMetadata);
+
+ GenerateTypeMetadata(typeMetadata.CollectionKeyTypeMetadata);
+ GenerateTypeMetadata(typeMetadata.CollectionValueTypeMetadata);
+ }
+ break;
+ case ClassType.Object:
+ {
+ source = GenerateForObject(typeMetadata);
+
+ if (typeMetadata.PropertiesMetadata != null)
+ {
+ foreach (PropertyMetadata metadata in typeMetadata.PropertiesMetadata)
+ {
+ GenerateTypeMetadata(metadata.TypeMetadata);
+ }
+ }
+ }
+ break;
+ case ClassType.TypeUnsupportedBySourceGen:
+ {
+ _executionContext.ReportDiagnostic(
+ Diagnostic.Create(TypeNotSupported, Location.None, new string[] { typeMetadata.CompilableName }));
+ return;
+ }
+ default:
+ {
+ throw new InvalidOperationException();
+ }
+ }
+
+ try
+ {
+ _executionContext.AddSource($"{typeMetadata.FriendlyName}.cs", SourceText.From(source, Encoding.UTF8));
+ }
+ catch (ArgumentException)
+ {
+ _executionContext.ReportDiagnostic(Diagnostic.Create(DuplicateTypeName, Location.None, new string[] { typeMetadata.FriendlyName }));
+ }
+ }
+
+ private string GenerateForTypeWithKnownConverter(TypeMetadata typeMetadata)
+ {
+ string typeCompilableName = typeMetadata.CompilableName;
+ string typeFriendlyName = typeMetadata.FriendlyName;
+
+ string metadataInitSource = $@"_{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, {JsonMetadataServicesClassName}.{typeFriendlyName}Converter);";
+
+ return GenerateForType(typeMetadata, metadataInitSource);
+ }
+
+ private string GenerateForTypeWithUnknownConverter(TypeMetadata typeMetadata)
+ {
+ string typeCompilableName = typeMetadata.CompilableName;
+ string typeFriendlyName = typeMetadata.FriendlyName;
+
+ StringBuilder sb = new();
+
+ string metadataInitSource = $@"JsonConverter converter = {typeMetadata.ConverterInstantiationLogic};
+ // TODO: consider moving this verification source to common helper.
+ Type typeToConvert = typeof({typeCompilableName});
+ if (!converter.CanConvert(typeToConvert))
+ {{
+ Type underlyingType = Nullable.GetUnderlyingType(typeToConvert);
+ if (underlyingType != null && converter.CanConvert(underlyingType))
+ {{
+ JsonConverter actualConverter = converter;
+
+ if (converter is JsonConverterFactory converterFactory)
+ {{
+ actualConverter = converterFactory.CreateConverter(underlyingType, {OptionsInstanceVariableName});
+
+ if (actualConverter == null || actualConverter is JsonConverterFactory)
+ {{
+ throw new InvalidOperationException($""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);
+ }}
+ else
+ {{
+ throw new InvalidOperationException($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'."");
+ }}
+ }}
+
+ _{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, converter);";
+
+ return GenerateForType(typeMetadata, metadataInitSource);
+ }
+
+ private string GenerateForNullable(TypeMetadata typeMetadata)
+ {
+ string typeCompilableName = typeMetadata.CompilableName;
+ string typeFriendlyName = typeMetadata.FriendlyName;
+
+ TypeMetadata? underlyingTypeMetadata = typeMetadata.NullableUnderlyingTypeMetadata;
+ Debug.Assert(underlyingTypeMetadata != null);
+ string underlyingTypeCompilableName = underlyingTypeMetadata.CompilableName;
+ string underlyingTypeFriendlyName = underlyingTypeMetadata.FriendlyName;
+ string underlyingTypeInfoNamedArg = underlyingTypeMetadata.ClassType == ClassType.TypeUnsupportedBySourceGen
+ ? "underlyingTypeInfo: null"
+ : $"underlyingTypeInfo: {underlyingTypeFriendlyName}";
+
+ string metadataInitSource = @$"_{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}(
+ {OptionsInstanceVariableName},
+ {JsonMetadataServicesClassName}.GetNullableConverter<{underlyingTypeCompilableName}>({underlyingTypeInfoNamedArg}));
+";
+
+ return GenerateForType(typeMetadata, metadataInitSource);
+ }
+
+ private string GenerateForEnum(TypeMetadata typeMetadata)
+ {
+ string typeCompilableName = typeMetadata.CompilableName;
+ string typeFriendlyName = typeMetadata.FriendlyName;
+
+ string metadataInitSource = $"_{typeFriendlyName} = {JsonMetadataServicesClassName}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, JsonMetadataServices.GetEnumConverter<{typeCompilableName}>({OptionsInstanceVariableName}));";
+
+ return GenerateForType(typeMetadata, metadataInitSource);
+ }
+
+ private string GenerateForCollection(TypeMetadata typeMetadata)
+ {
+ string typeCompilableName = typeMetadata.CompilableName;
+ string typeFriendlyName = typeMetadata.FriendlyName;
+
+ // Key metadata
+ TypeMetadata? collectionKeyTypeMetadata = typeMetadata.CollectionKeyTypeMetadata;
+ Debug.Assert(!(typeMetadata.CollectionType == CollectionType.Dictionary && collectionKeyTypeMetadata == null));
+ string? keyTypeCompilableName = collectionKeyTypeMetadata?.CompilableName;
+ string? keyTypeReadableName = collectionKeyTypeMetadata?.FriendlyName;
+
+ string? keyTypeMetadataPropertyName;
+ if (typeMetadata.ClassType != ClassType.Dictionary)
+ {
+ keyTypeMetadataPropertyName = "null";
+ }
+ else
+ {
+ keyTypeMetadataPropertyName = collectionKeyTypeMetadata.ClassType == ClassType.TypeUnsupportedBySourceGen
+ ? "null"
+ : $"this.{keyTypeReadableName}";
+ }
+
+ // Value metadata
+ TypeMetadata? collectionValueTypeMetadata = typeMetadata.CollectionValueTypeMetadata;
+ Debug.Assert(collectionValueTypeMetadata != null);
+ string valueTypeCompilableName = collectionValueTypeMetadata.CompilableName;
+ string valueTypeReadableName = collectionValueTypeMetadata.FriendlyName;
+
+ string valueTypeMetadataPropertyName = collectionValueTypeMetadata.ClassType == ClassType.TypeUnsupportedBySourceGen
+ ? "null"
+ : $"this.{valueTypeReadableName}";
+
+ string numberHandlingArg = $"{GetNumberHandlingAsStr(typeMetadata.NumberHandling)}";
+
+ 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})",
+ _ => throw new NotSupportedException()
+ };
+
+ string metadataInitSource = @$"_{typeFriendlyName} = {collectionTypeInfoValue};";
+ return GenerateForType(typeMetadata, metadataInitSource);
+ }
+
+ private string GenerateForObject(TypeMetadata typeMetadata)
+ {
+ string typeCompilableName = typeMetadata.CompilableName;
+ string typeFriendlyName = typeMetadata.FriendlyName;
+
+ string createObjectFuncTypeArg = typeMetadata.ConstructionStrategy == ObjectConstructionStrategy.ParameterlessConstructor
+ ? $"createObjectFunc: static () => new {typeMetadata.CompilableName}()"
+ : "createObjectFunc: null";
+
+ List? properties = typeMetadata.PropertiesMetadata;
+
+ StringBuilder sb = new();
+
+ sb.Append($@"JsonTypeInfo<{typeCompilableName}> objectInfo = {JsonMetadataServicesClassName}.CreateObjectInfo<{typeCompilableName}>();
+ _{typeFriendlyName} = objectInfo;
+");
+
+ string propInitFuncVarName = $"{typeFriendlyName}{PropInitFuncVarName}";
+
+ sb.Append($@"
+ {JsonMetadataServicesClassName}.InitializeObjectInfo(
+ objectInfo,
+ {OptionsInstanceVariableName},
+ {createObjectFuncTypeArg},
+ {propInitFuncVarName},
+ {GetNumberHandlingAsStr(typeMetadata.NumberHandling)});");
+
+ string metadataInitSource = sb.ToString();
+ string? propInitFuncSource = GeneratePropMetadataInitFunc(typeMetadata.IsValueType, propInitFuncVarName, properties);
+
+ return GenerateForType(typeMetadata, metadataInitSource, propInitFuncSource);
+ }
+
+ private string GeneratePropMetadataInitFunc(
+ bool declaringTypeIsValueType,
+ string propInitFuncVarName,
+ List? 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}]";
+
+ StringBuilder sb = new();
+
+ sb.Append($@"
+ private static {JsonPropertyInfoTypeName}[] {propInitFuncVarName}(JsonSerializerContext context)
+ {{
+ JsonContext {JsonContextVarName} = (JsonContext)context;
+ JsonSerializerOptions options = context.Options;
+
+ {JsonPropertyInfoTypeName}[] {PropVarName} = {propertyArrayInstantiationValue};
+");
+
+ if (properties != null)
+ {
+ for (int i = 0; i < properties.Count; i++)
+ {
+ PropertyMetadata memberMetadata = properties[i];
+
+ TypeMetadata memberTypeMetadata = memberMetadata.TypeMetadata;
+
+ string clrPropertyName = memberMetadata.ClrName;
+
+ string declaringTypeCompilableName = memberMetadata.DeclaringTypeCompilableName;
+
+ string memberTypeFriendlyName = memberTypeMetadata.ClassType == ClassType.TypeUnsupportedBySourceGen
+ ? "null"
+ : $"{JsonContextVarName}.{memberTypeMetadata.FriendlyName}";
+
+ string typeTypeInfoNamedArg = $"propertyTypeInfo: {memberTypeFriendlyName}";
+
+ string jsonPropertyNameNamedArg = memberMetadata.JsonPropertyName != null
+ ? @$"jsonPropertyName: ""{memberMetadata.JsonPropertyName}"""
+ : "jsonPropertyName: null";
+
+ string getterNamedArg = memberMetadata.HasGetter
+ ? $"getter: static (obj) => {{ return (({declaringTypeCompilableName})obj).{clrPropertyName}; }}"
+ : "getter: null";
+
+ string setterNamedArg;
+ if (memberMetadata.HasSetter)
+ {
+ string propMutation = declaringTypeIsValueType
+ ? @$"{{ Unsafe.Unbox<{declaringTypeCompilableName}>(obj).{clrPropertyName} = value; }}"
+ : $@"{{ (({declaringTypeCompilableName})obj).{clrPropertyName} = value; }}";
+
+ setterNamedArg = $"setter: static (obj, value) => {propMutation}";
+ }
+ else
+ {
+ setterNamedArg = "setter: null";
+ }
+
+ JsonIgnoreCondition? ignoreCondition = memberMetadata.IgnoreCondition;
+ string ignoreConditionNamedArg = ignoreCondition.HasValue
+ ? $"ignoreCondition: JsonIgnoreCondition.{ignoreCondition.Value}"
+ : "ignoreCondition: default";
+
+ string converterNamedArg = memberMetadata.ConverterInstantiationLogic == null
+ ? "converter: null"
+ : $"converter: {memberMetadata.ConverterInstantiationLogic}";
+
+ string memberTypeCompilableName = memberTypeMetadata.CompilableName;
+
+ 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});
+ ");
+ }
+ }
+
+ sb.Append(@$"
+ return {PropVarName};
+ }}");
+
+ return sb.ToString();
+ }
+
+ private string GenerateForType(TypeMetadata typeMetadata, string metadataInitSource, string? additionalSource = null)
+ {
+ string typeCompilableName = typeMetadata.CompilableName;
+ string typeFriendlyName = typeMetadata.FriendlyName;
+
+ return @$"{GetUsingStatementsString(typeMetadata)}
+
+namespace {_generationNamespace}
+{{
+ {JsonContextDeclarationSource}
+ {{
+ private JsonTypeInfo<{typeCompilableName}> _{typeFriendlyName};
+ public JsonTypeInfo<{typeCompilableName}> {typeFriendlyName}
+ {{
+ get
+ {{
+ if (_{typeFriendlyName} == null)
+ {{
+ {WrapWithCheckForCustomConverterIfRequired(metadataInitSource, typeCompilableName, typeFriendlyName, GetNumberHandlingAsStr(typeMetadata.NumberHandling))}
+ }}
+
+ return _{typeFriendlyName};
+ }}
+ }}{additionalSource}
+ }}
+}}
+";
+ }
+
+ private string WrapWithCheckForCustomConverterIfRequired(string source, string typeCompilableName, string typeFriendlyName, string numberHandlingNamedArg)
+ {
+ if (!_honorRuntimeProvidedCustomConverters)
+ {
+ 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} ")}
+ }}";
+ }
+
+ // Base source generation context partial class.
+ private string BaseJsonContextImplementation()
+ {
+ 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());
+
+ public JsonContext() : base(null)
+ {{
+ }}
+
+ public JsonContext(JsonSerializerOptions options) : base(options)
+ {{
+ }}
+
+ {GetFetchLogicForRuntimeSpecifiedCustomConverter()}
+ }}
+}}
+");
+
+ return sb.ToString();
+ }
+
+ private string GetFetchLogicForRuntimeSpecifiedCustomConverter()
+ {
+ if (!_honorRuntimeProvidedCustomConverters)
+ {
+ return "";
+ }
+
+ return @$"private JsonConverter {RuntimeCustomConverterFetchingMethodName}(System.Type type)
+ {{
+ System.Collections.Generic.IList converters = {OptionsInstanceVariableName}.Converters;
+
+ // TODO: use a dictionary if count > ~15.
+ for (int i = 0; i < converters.Count; i++)
+ {{
+ JsonConverter converter = converters[i];
+
+ if (converter.CanConvert(type))
+ {{
+ 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;
+ }}
+ }}
+
+ return null;
+ }}";
+ }
+
+ private string GetGetTypeInfoImplementation()
+ {
+ StringBuilder sb = new();
+
+ HashSet 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)
+ {{");
+
+ // TODO: Make this Dictionary-lookup-based if _handledType.Count > 64.
+ foreach (TypeMetadata metadata in _rootSerializableTypes.Values)
+ {
+ if (metadata.ClassType != ClassType.TypeUnsupportedBySourceGen)
+ {
+ sb.Append($@"
+ if (type == typeof({metadata.Type.GetUniqueCompilableTypeName()}))
+ {{
+ return this.{metadata.FriendlyName};
+ }}
+");
+ }
+ }
+
+ sb.Append(@"
+ return null!;
+ }
+ }
+}
+");
+
+ return sb.ToString();
+ }
+
+ private static string GetUsingStatementsString(TypeMetadata typeMetadata)
+ {
+ HashSet usingStatements = GetUsingStatements(typeMetadata);
+ return GetUsingStatementsString(usingStatements);
+ }
+
+ private static string GetUsingStatementsString(HashSet usingStatements)
+ {
+ string[] usingsArr = usingStatements.ToArray();
+ Array.Sort(usingsArr);
+ return string.Join("\n", usingsArr);
+ }
+
+ private static HashSet GetUsingStatements(TypeMetadata typeMetadata)
+ {
+ HashSet 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;
+ }
+
+ void AddUsingStatementsForType(TypeMetadata typeMetadata)
+ {
+ 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));
+ }
+ }
+
+ return usingStatements;
+ }
+
+ private static string FormatAsUsingStatement(string @namespace) => $"using {@namespace};";
+
+ private static string GetNumberHandlingAsStr(JsonNumberHandling? numberHandling) =>
+ numberHandling.HasValue
+ ? $"(JsonNumberHandling){(int)numberHandling.Value}"
+ : "default";
+
+ private static string GetCreateValueInfoMethodRef(string typeCompilableName) => $"{CreateValueInfoMethodName}<{typeCompilableName}>";
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGeneratorHelper.cs b/src/libraries/System.Text.Json/gen/JsonSourceGeneratorHelper.cs
new file mode 100644
index 00000000000000..10f1dd2f011a6e
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGeneratorHelper.cs
@@ -0,0 +1,467 @@
+// 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;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Reflection;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+using System.Text.Json.Serialization;
+using System.Text.Json.SourceGeneration.Reflection;
+using System.Linq;
+
+namespace System.Text.Json.SourceGeneration
+{
+ internal sealed partial class JsonSourceGeneratorHelper
+ {
+ private readonly Type _ienumerableType;
+ private readonly Type _listOfTType;
+ private readonly Type _dictionaryType;
+
+ private readonly Type _booleanType;
+ private readonly Type _byteArrayType;
+ private readonly Type _charType;
+ private readonly Type _dateTimeType;
+ private readonly Type _dateTimeOffsetType;
+ private readonly Type _guidType;
+ private readonly Type _stringType;
+ private readonly Type _uriType;
+ private readonly Type _versionType;
+
+ private readonly HashSet _numberTypes = new();
+
+ private readonly HashSet _knownTypes = new();
+
+ ///
+ /// Type information for member types in input object graphs.
+ ///
+ private readonly Dictionary _typeMetadataCache = new();
+
+ ///
+ /// Types that were specified with System.Text.Json.Serialization.JsonSerializableAttribute.
+ ///
+ private Dictionary _rootSerializableTypes;
+
+ private readonly GeneratorExecutionContext _executionContext;
+
+ private readonly MetadataLoadContextInternal _metadataLoadContext;
+
+ private const string SystemTextJsonNamespace = "System.Text.Json";
+
+ private const string JsonConverterAttributeFullName = "System.Text.Json.Serialization.JsonConverterAttribute";
+
+ private const string JsonIgnoreAttributeFullName = "System.Text.Json.Serialization.JsonIgnoreAttribute";
+
+ private const string JsonIgnoreConditionFullName = "System.Text.Json.Serialization.JsonIgnoreCondition";
+
+ private const string JsonIncludeAttributeFullName = "System.Text.Json.Serialization.JsonIncludeAttribute";
+
+ private const string JsonNumberHandlingAttributeFullName = "System.Text.Json.Serialization.JsonNumberHandlingAttribute";
+
+ private const string JsonPropertyNameAttributeFullName = "System.Text.Json.Serialization.JsonPropertyNameAttribute";
+
+ public JsonSourceGeneratorHelper(
+ GeneratorExecutionContext executionContext,
+ MetadataLoadContextInternal metadataLoadContext)
+ {
+ _generationNamespace = $"{executionContext.Compilation.AssemblyName}.JsonSourceGeneration";
+ _executionContext = executionContext;
+ _metadataLoadContext = metadataLoadContext;
+
+ _ienumerableType = metadataLoadContext.Resolve(typeof(IEnumerable));
+ _listOfTType = metadataLoadContext.Resolve(typeof(List<>));
+ _dictionaryType = metadataLoadContext.Resolve(typeof(Dictionary<,>));
+
+ _booleanType = metadataLoadContext.Resolve(typeof(bool));
+ _byteArrayType = metadataLoadContext.Resolve(typeof(byte[]));
+ _charType = metadataLoadContext.Resolve(typeof(char));
+ _dateTimeType = metadataLoadContext.Resolve(typeof(DateTime));
+ _dateTimeOffsetType = metadataLoadContext.Resolve(typeof(DateTimeOffset));
+ _guidType = metadataLoadContext.Resolve(typeof(Guid));
+ _stringType = metadataLoadContext.Resolve(typeof(string));
+ _uriType = metadataLoadContext.Resolve(typeof(Uri));
+ _versionType = metadataLoadContext.Resolve(typeof(Version));
+
+ PopulateKnownTypes(metadataLoadContext);
+ }
+
+ public void RegisterRootSerializableType(Type type, string? typeInfoPropertyName)
+ {
+ _rootSerializableTypes ??= new Dictionary();
+ _rootSerializableTypes[type.FullName] = GetOrAddTypeMetadata(type, typeInfoPropertyName);
+ }
+
+ public void GenerateSerializationMetadata()
+ {
+ if (_rootSerializableTypes == null || _rootSerializableTypes.Count == 0)
+ {
+ return;
+ }
+
+ foreach (KeyValuePair pair in _rootSerializableTypes)
+ {
+ TypeMetadata typeMetadata = pair.Value;
+ GenerateTypeMetadata(typeMetadata);
+ }
+
+ // Add base default instance source.
+ _executionContext.AddSource("JsonContext.g.cs", SourceText.From(BaseJsonContextImplementation(), Encoding.UTF8));
+
+ // Add GetJsonTypeInfo override implementation.
+ _executionContext.AddSource("JsonContext.GetJsonTypeInfo.g.cs", SourceText.From(GetGetTypeInfoImplementation(), Encoding.UTF8));
+ }
+
+ private TypeMetadata GetOrAddTypeMetadata(Type type, string? typeInfoPropertyName = null)
+ {
+ if (_typeMetadataCache.TryGetValue(type, out TypeMetadata? 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;
+
+ ClassType classType;
+ Type? collectionKeyType = null;
+ Type? collectionValueType = null;
+ Type? nullableUnderlyingType = null;
+ List? propertiesMetadata = null;
+ CollectionType collectionType = CollectionType.NotApplicable;
+ ObjectConstructionStrategy constructionStrategy = default;
+ JsonNumberHandling? numberHandling = null;
+ bool containsOnlyPrimitives = true;
+
+ bool foundDesignTimeCustomConverter = false;
+ string? converterInstatiationLogic = null;
+
+ IList attributeDataList = CustomAttributeData.GetCustomAttributes(type);
+ foreach (CustomAttributeData attributeData in attributeDataList)
+ {
+ Type attributeType = attributeData.AttributeType;
+ if (attributeType.FullName == "System.Text.Json.Serialization.JsonNumberHandlingAttribute")
+ {
+ IList ctorArgs = attributeData.ConstructorArguments;
+ numberHandling = (JsonNumberHandling)ctorArgs[0].Value;
+ continue;
+ }
+ else if (!foundDesignTimeCustomConverter && attributeType.GetCompatibleBaseClass(JsonConverterAttributeFullName) != null)
+ {
+ foundDesignTimeCustomConverter = true;
+ converterInstatiationLogic = GetConverterInstantiationLogic(attributeData);
+ }
+ }
+
+ if (foundDesignTimeCustomConverter)
+ {
+ classType = converterInstatiationLogic != null
+ ? ClassType.TypeWithDesignTimeProvidedCustomConverter
+ : ClassType.TypeUnsupportedBySourceGen;
+ }
+ else if (_knownTypes.Contains(type))
+ {
+ classType = ClassType.KnownType;
+ }
+ else if (type.IsNullableValueType(out nullableUnderlyingType))
+ {
+ Debug.Assert(nullableUnderlyingType != null);
+ classType = ClassType.Nullable;
+ }
+ else if (type.IsEnum)
+ {
+ classType = ClassType.Enum;
+ }
+ else if (_ienumerableType.IsAssignableFrom(type))
+ {
+ // Only T[], List, and Dictionary are supported.
+
+ if (type.IsArray)
+ {
+ classType = ClassType.Enumerable;
+ collectionType = CollectionType.Array;
+ collectionValueType = type.GetElementType();
+ }
+ else if (!type.IsGenericType)
+ {
+ classType = ClassType.TypeUnsupportedBySourceGen;
+ }
+ else
+ {
+ Type genericTypeDef = type.GetGenericTypeDefinition();
+ Type[] genericTypeArgs = type.GetGenericArguments();
+
+ if (genericTypeDef == _listOfTType)
+ {
+ classType = ClassType.Enumerable;
+ collectionType = CollectionType.List;
+ collectionValueType = genericTypeArgs[0];
+ }
+ else if (genericTypeDef == _dictionaryType)
+ {
+ classType = ClassType.Dictionary;
+ collectionType = CollectionType.Dictionary;
+ collectionKeyType = genericTypeArgs[0];
+ collectionValueType = genericTypeArgs[1];
+ }
+ else
+ {
+ classType = ClassType.TypeUnsupportedBySourceGen;
+ }
+ }
+ }
+ else
+ {
+ classType = ClassType.Object;
+
+ if (type.GetConstructor(Type.EmptyTypes) != null && !type.IsAbstract && !type.IsInterface)
+ {
+ // TODO: support parameterized ctors.
+ constructionStrategy = ObjectConstructionStrategy.ParameterlessConstructor;
+ }
+
+ for (Type? currentType = type; currentType != null; currentType = currentType.BaseType)
+ {
+ const BindingFlags bindingFlags =
+ BindingFlags.Instance |
+ BindingFlags.Public |
+ BindingFlags.NonPublic |
+ BindingFlags.DeclaredOnly;
+
+ foreach (PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags))
+ {
+ PropertyMetadata metadata = GetPropertyMetadata(propertyInfo);
+
+ // Ignore indexers.
+ if (propertyInfo.GetIndexParameters().Length > 0)
+ {
+ continue;
+ }
+
+ string key = metadata.JsonPropertyName ?? metadata.ClrName;
+
+ if (metadata.HasGetter || metadata.HasSetter)
+ {
+ (propertiesMetadata ??= new()).Add(metadata);
+ }
+
+ if (containsOnlyPrimitives && !IsPrimitive(propertyInfo.PropertyType))
+ {
+ containsOnlyPrimitives = false;
+ }
+ }
+
+ foreach (FieldInfo fieldInfo in currentType.GetFields(bindingFlags))
+ {
+ PropertyMetadata metadata = GetPropertyMetadata(fieldInfo);
+
+ if (metadata.HasGetter || metadata.HasSetter)
+ {
+ (propertiesMetadata ??= new()).Add(metadata);
+ }
+ }
+ }
+ }
+
+ typeMetadata.Initialize(
+ compilableName: type.GetUniqueCompilableTypeName(),
+ friendlyName: typeInfoPropertyName ?? type.GetFriendlyTypeName(),
+ type,
+ classType,
+ isValueType: type.IsValueType,
+ numberHandling,
+ propertiesMetadata,
+ collectionType,
+ collectionKeyTypeMetadata: collectionKeyType != null ? GetOrAddTypeMetadata(collectionKeyType) : null,
+ collectionValueTypeMetadata: collectionValueType != null ? GetOrAddTypeMetadata(collectionValueType) : null,
+ constructionStrategy,
+ nullableUnderlyingTypeMetadata: nullableUnderlyingType != null ? GetOrAddTypeMetadata(nullableUnderlyingType) : null,
+ converterInstatiationLogic,
+ containsOnlyPrimitives);
+
+ return typeMetadata;
+ }
+
+ private PropertyMetadata GetPropertyMetadata(MemberInfo memberInfo)
+ {
+ IList attributeDataList = CustomAttributeData.GetCustomAttributes(memberInfo);
+
+ bool hasJsonInclude = false;
+ JsonIgnoreCondition? ignoreCondition = null;
+ JsonNumberHandling? numberHandling = null;
+ string? jsonPropertyName = null;
+
+ bool foundDesignTimeCustomConverter = false;
+ string? converterInstantiationLogic = null;
+
+ foreach (CustomAttributeData attributeData in attributeDataList)
+ {
+ Type attributeType = attributeData.AttributeType;
+
+ if (!foundDesignTimeCustomConverter && attributeType.GetCompatibleBaseClass(JsonConverterAttributeFullName) != null)
+ {
+ foundDesignTimeCustomConverter = true;
+ converterInstantiationLogic = GetConverterInstantiationLogic(attributeData);
+ }
+ else if (attributeType.Assembly.FullName == SystemTextJsonNamespace)
+ {
+ switch (attributeData.AttributeType.FullName)
+ {
+ case JsonIgnoreAttributeFullName:
+ {
+ IList namedArgs = attributeData.NamedArguments;
+
+ if (namedArgs.Count == 0)
+ {
+ ignoreCondition = JsonIgnoreCondition.Always;
+ }
+ else if (namedArgs.Count == 1 &&
+ namedArgs[0].MemberInfo.MemberType == MemberTypes.Property &&
+ ((PropertyInfo)namedArgs[0].MemberInfo).PropertyType.FullName == JsonIgnoreConditionFullName)
+ {
+ ignoreCondition = (JsonIgnoreCondition)namedArgs[0].TypedValue.Value;
+ }
+ }
+ break;
+ case JsonIncludeAttributeFullName:
+ {
+ hasJsonInclude = true;
+ }
+ break;
+ case JsonNumberHandlingAttributeFullName:
+ {
+ IList ctorArgs = attributeData.ConstructorArguments;
+ numberHandling = (JsonNumberHandling)ctorArgs[0].Value;
+ }
+ break;
+ case JsonPropertyNameAttributeFullName:
+ {
+ IList ctorArgs = attributeData.ConstructorArguments;
+ jsonPropertyName = (string)ctorArgs[0].Value;
+ // Null check here is done at runtime within JsonSerializer.
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ Type memberCLRType;
+ bool hasGetter;
+ bool hasSetter;
+ bool getterIsVirtual = false;
+ bool setterIsVirtual = false;
+
+ switch (memberInfo)
+ {
+ case PropertyInfo propertyInfo:
+ {
+ MethodInfo setMethod = propertyInfo.SetMethod;
+
+ memberCLRType = propertyInfo.PropertyType;
+ hasGetter = PropertyAccessorCanBeReferenced(propertyInfo.GetMethod, hasJsonInclude);
+ hasSetter = PropertyAccessorCanBeReferenced(setMethod, hasJsonInclude) && !setMethod.IsInitOnly();
+ getterIsVirtual = propertyInfo.GetMethod?.IsVirtual == true;
+ setterIsVirtual = propertyInfo.SetMethod?.IsVirtual == true;
+ }
+ break;
+ case FieldInfo fieldInfo:
+ {
+ Debug.Assert(fieldInfo.IsPublic);
+
+ memberCLRType = fieldInfo.FieldType;
+ hasGetter = true;
+ hasSetter = !fieldInfo.IsInitOnly;
+ }
+ break;
+ default:
+ throw new InvalidOperationException();
+ }
+
+ return new PropertyMetadata
+ {
+ ClrName = memberInfo.Name,
+ IsProperty = memberInfo.MemberType == MemberTypes.Property,
+ JsonPropertyName = jsonPropertyName,
+ HasGetter = hasGetter,
+ HasSetter = hasSetter,
+ GetterIsVirtual = getterIsVirtual,
+ SetterIsVirtual = setterIsVirtual,
+ IgnoreCondition = ignoreCondition,
+ NumberHandling = numberHandling,
+ HasJsonInclude = hasJsonInclude,
+ TypeMetadata = GetOrAddTypeMetadata(memberCLRType),
+ DeclaringTypeCompilableName = memberInfo.DeclaringType.GetUniqueCompilableTypeName(),
+ ConverterInstantiationLogic = converterInstantiationLogic
+ };
+ }
+
+ private static bool PropertyAccessorCanBeReferenced(MethodInfo? memberAccessor, bool hasJsonInclude) =>
+ (memberAccessor != null && !memberAccessor.IsPrivate) && (memberAccessor.IsPublic || hasJsonInclude);
+
+ private string? GetConverterInstantiationLogic(CustomAttributeData attributeData)
+ {
+ if (attributeData.AttributeType.FullName != JsonConverterAttributeFullName)
+ {
+ return null;
+ }
+
+ Type converterType = new TypeWrapper((ITypeSymbol)attributeData.ConstructorArguments[0].Value, _metadataLoadContext);
+
+ if (converterType == null || converterType.GetConstructor(Type.EmptyTypes) == null || converterType.IsNestedPrivate)
+ {
+ return null;
+ }
+
+ return $"new {converterType.GetUniqueCompilableTypeName()}()";
+ }
+
+ private void PopulateNumberTypes(MetadataLoadContextInternal metadataLoadContext)
+ {
+ Debug.Assert(_numberTypes != null);
+ _numberTypes.Add(metadataLoadContext.Resolve(typeof(byte)));
+ _numberTypes.Add(metadataLoadContext.Resolve(typeof(decimal)));
+ _numberTypes.Add(metadataLoadContext.Resolve(typeof(double)));
+ _numberTypes.Add(metadataLoadContext.Resolve(typeof(short)));
+ _numberTypes.Add(metadataLoadContext.Resolve(typeof(sbyte)));
+ _numberTypes.Add(metadataLoadContext.Resolve(typeof(int)));
+ _numberTypes.Add(metadataLoadContext.Resolve(typeof(long)));
+ _numberTypes.Add(metadataLoadContext.Resolve(typeof(float)));
+ _numberTypes.Add(metadataLoadContext.Resolve(typeof(ushort)));
+ _numberTypes.Add(metadataLoadContext.Resolve(typeof(uint)));
+ _numberTypes.Add(metadataLoadContext.Resolve(typeof(ulong)));
+ }
+
+ private void PopulateKnownTypes(MetadataLoadContextInternal metadataLoadContext)
+ {
+ PopulateNumberTypes(metadataLoadContext);
+
+ Debug.Assert(_knownTypes != null);
+ Debug.Assert(_numberTypes != null);
+
+ _knownTypes.UnionWith(_numberTypes);
+ _knownTypes.Add(_booleanType);
+ _knownTypes.Add(_byteArrayType);
+ _knownTypes.Add(_charType);
+ _knownTypes.Add(_dateTimeType);
+ _knownTypes.Add(_dateTimeOffsetType);
+ _knownTypes.Add(_guidType);
+ _knownTypes.Add(metadataLoadContext.Resolve(typeof(object)));
+ _knownTypes.Add(_stringType);
+
+ // System.Private.Uri may not be loaded in input compilation.
+ if (_uriType != null)
+ {
+ _knownTypes.Add(_uriType);
+ }
+
+ _knownTypes.Add(metadataLoadContext.Resolve(typeof(Version)));
+ }
+
+ private bool IsPrimitive(Type type)
+ => _knownTypes.Contains(type) && type != _uriType && type != _versionType;
+
+ public Dictionary? GetSerializableTypes() => _rootSerializableTypes?.ToDictionary(p => p.Key, p => p.Value.Type);
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/ObjectConstructionStrategy.cs b/src/libraries/System.Text.Json/gen/ObjectConstructionStrategy.cs
new file mode 100644
index 00000000000000..03705d27075a35
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/ObjectConstructionStrategy.cs
@@ -0,0 +1,24 @@
+// 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.SourceGeneration
+{
+ ///
+ /// Indicates which kind of constructor an object is to be created with.
+ ///
+ internal enum ObjectConstructionStrategy
+ {
+ ///
+ /// Object is abstract or an interface.
+ ///
+ NotApplicable = 0,
+ ///
+ /// Object should be created with a parameterless constructor.
+ ///
+ ParameterlessConstructor = 1,
+ ///
+ /// Object should be created with a parameterized constructor.
+ ///
+ ParameterizedConstructor = 2,
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/PropertyMetadata.cs b/src/libraries/System.Text.Json/gen/PropertyMetadata.cs
new file mode 100644
index 00000000000000..68a5a408e83131
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/PropertyMetadata.cs
@@ -0,0 +1,73 @@
+// 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;
+
+namespace System.Text.Json.SourceGeneration
+{
+ [DebuggerDisplay("Name={Name}, Type={TypeMetadata}")]
+ internal class PropertyMetadata
+ {
+ ///
+ /// The CLR name of the property.
+ ///
+ public string ClrName { get; init; }
+
+ ///
+ /// Is this a property or a field?
+ ///
+ public bool IsProperty { get; init; }
+
+ ///
+ /// The property name specified via JsonPropertyNameAttribute, if available.
+ ///
+ public string? JsonPropertyName { get; init; }
+
+ ///
+ /// Whether the property has a public or internal (only usable when JsonIncludeAttribute is specified)
+ /// getter that can be referenced in generated source code.
+ ///
+ public bool HasGetter { get; init; }
+
+ ///
+ /// Whether the property has a public or internal (only usable when JsonIncludeAttribute is specified)
+ /// setter that can be referenced in generated source code.
+ ///
+ public bool HasSetter { get; init; }
+
+ public bool GetterIsVirtual { get; init; }
+
+ public bool SetterIsVirtual { get; init; }
+
+ ///
+ /// The for the property.
+ ///
+ public JsonIgnoreCondition? IgnoreCondition { get; init; }
+
+ ///
+ /// The for the property.
+ ///
+ public JsonNumberHandling? NumberHandling { get; init; }
+
+ ///
+ /// Whether the property has the JsonIncludeAttribute. If so, non-public accessors can be used for (de)serialziation.
+ ///
+ public bool HasJsonInclude { get; init; }
+
+ ///
+ /// Metadata for the property's type.
+ ///
+ public TypeMetadata TypeMetadata { get; init; }
+
+ ///
+ /// Compilable name of the property's declaring type.
+ ///
+ public string DeclaringTypeCompilableName { get; init; }
+
+ ///
+ /// Source code to instantiate design-time specified custom converter.
+ ///
+ public string? ConverterInstantiationLogic { get; init; }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/Reflection/AssemblyWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/AssemblyWrapper.cs
new file mode 100644
index 00000000000000..d5ed28e4800a17
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/Reflection/AssemblyWrapper.cs
@@ -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.Collections.Generic;
+using System.Reflection;
+using Microsoft.CodeAnalysis;
+
+namespace System.Text.Json.SourceGeneration.Reflection
+{
+ internal class AssemblyWrapper : Assembly
+ {
+ private readonly MetadataLoadContextInternal _metadataLoadContext;
+
+ public AssemblyWrapper(IAssemblySymbol assembly, MetadataLoadContextInternal metadataLoadContext)
+ {
+ Symbol = assembly;
+ _metadataLoadContext = metadataLoadContext;
+ }
+
+ internal IAssemblySymbol Symbol { get; }
+
+ public override string FullName => Symbol.Identity.Name;
+
+ public override Type[] GetExportedTypes()
+ {
+ return GetTypes();
+ }
+
+ public override Type[] GetTypes()
+ {
+ var types = new List();
+ var stack = new Stack();
+ stack.Push(Symbol.GlobalNamespace);
+ while (stack.Count > 0)
+ {
+ INamespaceSymbol current = stack.Pop();
+
+ foreach (INamedTypeSymbol type in current.GetTypeMembers())
+ {
+ types.Add(type.AsType(_metadataLoadContext));
+ }
+
+ foreach (INamespaceSymbol ns in current.GetNamespaceMembers())
+ {
+ stack.Push(ns);
+ }
+ }
+ return types.ToArray();
+ }
+
+ public override Type GetType(string name)
+ {
+ return Symbol.GetTypeByMetadataName(name)!.AsType(_metadataLoadContext);
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/Reflection/ConstructorInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/ConstructorInfoWrapper.cs
new file mode 100644
index 00000000000000..979d32c8f7c99e
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/Reflection/ConstructorInfoWrapper.cs
@@ -0,0 +1,94 @@
+// 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.Globalization;
+using System.Reflection;
+using Microsoft.CodeAnalysis;
+
+namespace System.Text.Json.SourceGeneration.Reflection
+{
+ internal class ConstructorInfoWrapper : ConstructorInfo
+ {
+ private readonly IMethodSymbol _ctor;
+ private readonly MetadataLoadContextInternal _metadataLoadContext;
+
+ public ConstructorInfoWrapper(IMethodSymbol ctor, MetadataLoadContextInternal metadataLoadContext)
+ {
+ _ctor = ctor;
+ _metadataLoadContext = metadataLoadContext;
+ }
+
+ public override Type DeclaringType => _ctor.ContainingType.AsType(_metadataLoadContext);
+
+ public override MethodAttributes Attributes => throw new NotImplementedException();
+
+ public override RuntimeMethodHandle MethodHandle => throw new NotSupportedException();
+
+ public override string Name => _ctor.Name;
+
+ public override Type ReflectedType => throw new NotImplementedException();
+
+ public override bool IsGenericMethod => _ctor.IsGenericMethod;
+
+ public override Type[] GetGenericArguments()
+ {
+ var typeArguments = new List();
+ foreach (ITypeSymbol t in _ctor.TypeArguments)
+ {
+ typeArguments.Add(t.AsType(_metadataLoadContext));
+ }
+ return typeArguments.ToArray();
+ }
+
+ public override IList GetCustomAttributesData()
+ {
+ var attributes = new List();
+ foreach (AttributeData a in _ctor.GetAttributes())
+ {
+ attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext));
+ }
+ return attributes;
+ }
+
+ public override object[] GetCustomAttributes(bool inherit)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override object[] GetCustomAttributes(Type attributeType, bool inherit)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override MethodImplAttributes GetMethodImplementationFlags()
+ {
+ throw new NotImplementedException();
+ }
+
+ public override ParameterInfo[] GetParameters()
+ {
+ var parameters = new List();
+ foreach (IParameterSymbol p in _ctor.Parameters)
+ {
+ parameters.Add(new ParameterInfoWrapper(p, _metadataLoadContext));
+ }
+ return parameters.ToArray();
+ }
+
+ public override object Invoke(BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override bool IsDefined(Type attributeType, bool inherit)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/Reflection/CustomAttributeDataWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/CustomAttributeDataWrapper.cs
new file mode 100644
index 00000000000000..75b31db587a8f6
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/Reflection/CustomAttributeDataWrapper.cs
@@ -0,0 +1,43 @@
+// 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.Linq;
+using System.Reflection;
+using Microsoft.CodeAnalysis;
+
+namespace System.Text.Json.SourceGeneration.Reflection
+{
+ internal class CustomAttributeDataWrapper : CustomAttributeData
+ {
+ public CustomAttributeDataWrapper(AttributeData a, MetadataLoadContextInternal metadataLoadContext)
+ {
+ var namedArguments = new List();
+ foreach (KeyValuePair na in a.NamedArguments)
+ {
+ var member = a.AttributeClass!.GetMembers(na.Key).First();
+
+ MemberInfo memberInfo = member is IPropertySymbol
+ ? new PropertyInfoWrapper((IPropertySymbol)member, metadataLoadContext)
+ : new FieldInfoWrapper((IFieldSymbol)member, metadataLoadContext);
+
+ namedArguments.Add(new CustomAttributeNamedArgument(memberInfo, na.Value.Value));
+ }
+
+ var constructorArguments = new List();
+ foreach (TypedConstant ca in a.ConstructorArguments)
+ {
+ constructorArguments.Add(new CustomAttributeTypedArgument(ca.Type.AsType(metadataLoadContext), ca.Value));
+ }
+ Constructor = new ConstructorInfoWrapper(a.AttributeConstructor!, metadataLoadContext);
+ NamedArguments = namedArguments;
+ ConstructorArguments = constructorArguments;
+ }
+
+ public override ConstructorInfo Constructor { get; }
+
+ public override IList NamedArguments { get; }
+
+ public override IList ConstructorArguments { get; }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/Reflection/FieldInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/FieldInfoWrapper.cs
new file mode 100644
index 00000000000000..cf36ceacbeee55
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/Reflection/FieldInfoWrapper.cs
@@ -0,0 +1,96 @@
+// 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.Reflection;
+using Microsoft.CodeAnalysis;
+using System.Globalization;
+
+namespace System.Text.Json.SourceGeneration.Reflection
+{
+ internal class FieldInfoWrapper : FieldInfo
+ {
+ private readonly IFieldSymbol _field;
+ private readonly MetadataLoadContextInternal _metadataLoadContext;
+ public FieldInfoWrapper(IFieldSymbol parameter, MetadataLoadContextInternal metadataLoadContext)
+ {
+ _field = parameter;
+ _metadataLoadContext = metadataLoadContext;
+ }
+
+ private FieldAttributes? _attributes;
+
+ public override FieldAttributes Attributes
+ {
+ get
+ {
+ if (!_attributes.HasValue)
+ {
+ _attributes = default(FieldAttributes);
+
+ if (_field.IsStatic)
+ {
+ _attributes |= FieldAttributes.Static;
+ }
+
+ switch (_field.DeclaredAccessibility)
+ {
+ case Accessibility.Public:
+ _attributes |= FieldAttributes.Public;
+ break;
+ case Accessibility.Private:
+ _attributes |= FieldAttributes.Private;
+ break;
+ }
+ }
+
+ return _attributes.Value;
+ }
+ }
+
+ public override RuntimeFieldHandle FieldHandle => throw new NotImplementedException();
+
+ public override Type FieldType => _field.Type.AsType(_metadataLoadContext);
+
+ public override Type DeclaringType => _field.ContainingType.AsType(_metadataLoadContext);
+
+ public override string Name => _field.Name;
+
+ public override Type ReflectedType => throw new NotImplementedException();
+
+ public override object[] GetCustomAttributes(bool inherit)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override object[] GetCustomAttributes(Type attributeType, bool inherit)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override object GetValue(object obj)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override IList GetCustomAttributesData()
+ {
+ var attributes = new List();
+ foreach (AttributeData a in _field.GetAttributes())
+ {
+ attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext));
+ }
+ return attributes;
+ }
+
+ public override bool IsDefined(Type attributeType, bool inherit)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/Reflection/MemberInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/MemberInfoWrapper.cs
new file mode 100644
index 00000000000000..1b4de7811fd741
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/Reflection/MemberInfoWrapper.cs
@@ -0,0 +1,54 @@
+// 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.Reflection;
+using Microsoft.CodeAnalysis;
+
+namespace System.Text.Json.SourceGeneration.Reflection
+{
+ internal class MemberInfoWrapper : MemberInfo
+ {
+ private readonly ISymbol _member;
+ private readonly MetadataLoadContextInternal _metadataLoadContext;
+
+ public MemberInfoWrapper(ISymbol member, MetadataLoadContextInternal metadataLoadContext)
+ {
+ _member = member;
+ _metadataLoadContext = metadataLoadContext;
+ }
+
+ public override Type DeclaringType => _member.ContainingType.AsType(_metadataLoadContext);
+
+ public override MemberTypes MemberType => throw new NotImplementedException();
+
+ public override string Name => _member.Name;
+
+ public override Type ReflectedType => throw new NotImplementedException();
+
+ public override IList GetCustomAttributesData()
+ {
+ var attributes = new List();
+ foreach (AttributeData a in _member.GetAttributes())
+ {
+ attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext));
+ }
+ return attributes;
+ }
+
+ public override object[] GetCustomAttributes(bool inherit)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override object[] GetCustomAttributes(Type attributeType, bool inherit)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override bool IsDefined(Type attributeType, bool inherit)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs b/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs
new file mode 100644
index 00000000000000..885885325fe53b
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs
@@ -0,0 +1,114 @@
+// 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;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using Microsoft.CodeAnalysis;
+
+namespace System.Text.Json.SourceGeneration.Reflection
+{
+ internal class MetadataLoadContextInternal
+ {
+ private readonly Dictionary _assemblies = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ private readonly Compilation _compilation;
+
+ private IAssemblySymbol? _collectionsAssemblySymbol;
+
+ public MetadataLoadContextInternal(Compilation compilation)
+ {
+ _compilation = compilation;
+ Dictionary assemblies = compilation.References
+ .OfType()
+ .ToDictionary(
+ r => string.IsNullOrWhiteSpace(r.FilePath) ?
+ new AssemblyName(r.Display) : AssemblyName.GetAssemblyName(r.FilePath),
+ r => (IAssemblySymbol)compilation.GetAssemblyOrModuleSymbol(r)!);
+
+ foreach (var item in assemblies)
+ {
+ string key = item.Key.Name;
+ _assemblies[key] = item.Value!;
+
+ if (_collectionsAssemblySymbol == null && key == "System.Collections")
+ {
+ _collectionsAssemblySymbol = item.Value!;
+ }
+ }
+
+ CoreAssembly = new AssemblyWrapper(compilation.GetTypeByMetadataName("System.Object")!.ContainingAssembly, this);
+ MainAssembly = new AssemblyWrapper(compilation.Assembly, this);
+ }
+
+ public Type Resolve() => Resolve(typeof(T));
+
+ public Type? Resolve(Type type)
+ {
+ string assemblyName = type.Assembly.GetName().Name;
+ IAssemblySymbol assemblySymbol;
+
+ if (assemblyName == "System.Private.CoreLib" || assemblyName == "mscorlib" || assemblyName == "System.Runtime" || assemblyName == "System.Private.Uri")
+ {
+ Type resolvedType = ResolveFromAssembly(type, CoreAssembly.Symbol);
+ if (resolvedType != null)
+ {
+ return resolvedType;
+ }
+
+ if (_collectionsAssemblySymbol != null && typeof(IEnumerable).IsAssignableFrom(type))
+ {
+ resolvedType = ResolveFromAssembly(type, _collectionsAssemblySymbol);
+ if (resolvedType != null)
+ {
+ return resolvedType;
+ }
+ }
+ }
+
+ CustomAttributeData? typeForwardedFrom = type.GetCustomAttributeData(typeof(TypeForwardedFromAttribute));
+ if (typeForwardedFrom != null)
+ {
+ assemblyName = typeForwardedFrom.GetConstructorArgument(0);
+ }
+
+ if (!_assemblies.TryGetValue(new AssemblyName(assemblyName).Name, out assemblySymbol))
+ {
+ return null;
+ }
+
+ return ResolveFromAssembly(type, assemblySymbol);
+ }
+
+ private Type? ResolveFromAssembly(Type type, IAssemblySymbol assemblySymbol)
+ {
+ if (type.IsArray)
+ {
+ var typeSymbol = assemblySymbol.GetTypeByMetadataName(type.GetElementType().FullName);
+ if (typeSymbol == null)
+ {
+ return null!;
+ }
+
+ return _compilation.CreateArrayTypeSymbol(typeSymbol).AsType(this);
+ }
+
+ // Resolve the full name
+ return assemblySymbol.GetTypeByMetadataName(type.FullName)!.AsType(this);
+ }
+
+ private AssemblyWrapper CoreAssembly { get; }
+ public Assembly MainAssembly { get; }
+
+ internal Assembly LoadFromAssemblyName(string fullName)
+ {
+ if (_assemblies.TryGetValue(new AssemblyName(fullName).Name, out var assembly))
+ {
+ return new AssemblyWrapper(assembly, this);
+ }
+ return null!;
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/Reflection/MethodInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/MethodInfoWrapper.cs
new file mode 100644
index 00000000000000..44eecf0b5919db
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/Reflection/MethodInfoWrapper.cs
@@ -0,0 +1,138 @@
+// 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.Globalization;
+using System.Reflection;
+using Microsoft.CodeAnalysis;
+
+namespace System.Text.Json.SourceGeneration.Reflection
+{
+ internal class MethodInfoWrapper : MethodInfo
+ {
+ private readonly IMethodSymbol _method;
+ private readonly MetadataLoadContextInternal _metadataLoadContext;
+
+ public MethodInfoWrapper(IMethodSymbol method, MetadataLoadContextInternal metadataLoadContext)
+ {
+ _method = method;
+ _metadataLoadContext = metadataLoadContext;
+ }
+
+ public override ICustomAttributeProvider ReturnTypeCustomAttributes => throw new NotImplementedException();
+
+ private MethodAttributes? _attributes;
+
+ public override MethodAttributes Attributes
+ {
+ get
+ {
+ if (!_attributes.HasValue)
+ {
+ _attributes = default(MethodAttributes);
+
+ if (_method.IsAbstract)
+ {
+ _attributes |= MethodAttributes.Abstract;
+ }
+
+ if (_method.IsStatic)
+ {
+ _attributes |= MethodAttributes.Static;
+ }
+
+ if (_method.IsVirtual)
+ {
+ _attributes |= MethodAttributes.Virtual;
+ }
+
+ switch (_method.DeclaredAccessibility)
+ {
+ case Accessibility.Public:
+ _attributes |= MethodAttributes.Public;
+ break;
+ case Accessibility.Private:
+ _attributes |= MethodAttributes.Private;
+ break;
+ }
+ }
+
+ return _attributes.Value;
+ }
+ }
+
+ public override RuntimeMethodHandle MethodHandle => throw new NotSupportedException();
+
+ public override Type DeclaringType => _method.ContainingType.AsType(_metadataLoadContext);
+
+ public override Type ReturnType => _method.ReturnType.AsType(_metadataLoadContext);
+
+ public override string Name => _method.Name;
+
+ public override bool IsGenericMethod => _method.IsGenericMethod;
+
+ public bool IsInitOnly => _method.IsInitOnly;
+
+ public override Type ReflectedType => throw new NotImplementedException();
+
+ public override IList GetCustomAttributesData()
+ {
+ var attributes = new List();
+ foreach (AttributeData a in _method.GetAttributes())
+ {
+ attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext));
+ }
+ return attributes;
+ }
+
+ public override MethodInfo GetBaseDefinition()
+ {
+ throw new NotImplementedException();
+ }
+
+ public override object[] GetCustomAttributes(bool inherit)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override object[] GetCustomAttributes(Type attributeType, bool inherit)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override Type[] GetGenericArguments()
+ {
+ var typeArguments = new List();
+ foreach (ITypeSymbol t in _method.TypeArguments)
+ {
+ typeArguments.Add(t.AsType(_metadataLoadContext));
+ }
+ return typeArguments.ToArray();
+ }
+
+ public override MethodImplAttributes GetMethodImplementationFlags()
+ {
+ throw new NotImplementedException();
+ }
+
+ public override ParameterInfo[] GetParameters()
+ {
+ var parameters = new List();
+ foreach (IParameterSymbol p in _method.Parameters)
+ {
+ parameters.Add(new ParameterInfoWrapper(p, _metadataLoadContext));
+ }
+ return parameters.ToArray();
+ }
+
+ public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override bool IsDefined(Type attributeType, bool inherit)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/Reflection/ParameterInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/ParameterInfoWrapper.cs
new file mode 100644
index 00000000000000..b8891251c7e627
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/Reflection/ParameterInfoWrapper.cs
@@ -0,0 +1,36 @@
+// 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.Reflection;
+using Microsoft.CodeAnalysis;
+
+namespace System.Text.Json.SourceGeneration.Reflection
+{
+ internal class ParameterInfoWrapper : ParameterInfo
+ {
+ private readonly IParameterSymbol _parameter;
+
+ private readonly MetadataLoadContextInternal _metadataLoadContext;
+
+ public ParameterInfoWrapper(IParameterSymbol parameter, MetadataLoadContextInternal metadataLoadContext)
+ {
+ _parameter = parameter;
+ _metadataLoadContext = metadataLoadContext;
+ }
+
+ public override Type ParameterType => _parameter.Type.AsType(_metadataLoadContext);
+
+ public override string Name => _parameter.Name;
+
+ public override IList GetCustomAttributesData()
+ {
+ var attributes = new List();
+ foreach (AttributeData a in _parameter.GetAttributes())
+ {
+ attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext));
+ }
+ return attributes;
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/Reflection/PropertyInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/PropertyInfoWrapper.cs
new file mode 100644
index 00000000000000..294c6dd1059165
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/Reflection/PropertyInfoWrapper.cs
@@ -0,0 +1,96 @@
+// 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.Globalization;
+using System.Reflection;
+using Microsoft.CodeAnalysis;
+
+namespace System.Text.Json.SourceGeneration.Reflection
+{
+ internal class PropertyInfoWrapper : PropertyInfo
+ {
+ private readonly IPropertySymbol _property;
+ private MetadataLoadContextInternal _metadataLoadContext;
+
+ public PropertyInfoWrapper(IPropertySymbol property, MetadataLoadContextInternal metadataLoadContext)
+ {
+ _property = property;
+ _metadataLoadContext = metadataLoadContext;
+ }
+
+ public override PropertyAttributes Attributes => throw new NotImplementedException();
+
+ public override bool CanRead => _property.GetMethod != null;
+
+ public override bool CanWrite => _property.SetMethod != null;
+
+ public override Type PropertyType => _property.Type.AsType(_metadataLoadContext);
+
+ public override Type DeclaringType => _property.ContainingType.AsType(_metadataLoadContext);
+
+ public override string Name => _property.Name;
+
+ public override Type ReflectedType => throw new NotImplementedException();
+
+ public override MethodInfo[] GetAccessors(bool nonPublic)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override object[] GetCustomAttributes(bool inherit)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override object[] GetCustomAttributes(Type attributeType, bool inherit)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override IList GetCustomAttributesData()
+ {
+ var attributes = new List();
+ foreach (AttributeData a in _property.GetAttributes())
+ {
+ attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext));
+ }
+ return attributes;
+ }
+
+ public override MethodInfo GetGetMethod(bool nonPublic)
+ {
+ return _property.GetMethod!.AsMethodInfo(_metadataLoadContext);
+ }
+
+ public override ParameterInfo[] GetIndexParameters()
+ {
+ var parameters = new List();
+ foreach (IParameterSymbol p in _property.Parameters)
+ {
+ parameters.Add(new ParameterInfoWrapper(p, _metadataLoadContext));
+ }
+ return parameters.ToArray();
+ }
+
+ public override MethodInfo GetSetMethod(bool nonPublic)
+ {
+ return _property.SetMethod!.AsMethodInfo(_metadataLoadContext);
+ }
+
+ public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override bool IsDefined(Type attributeType, bool inherit)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture)
+ {
+ throw new NotSupportedException();
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs
new file mode 100644
index 00000000000000..07dce68fcbb899
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Linq;
+using System.Reflection;
+
+namespace System.Text.Json.SourceGeneration.Reflection
+{
+ internal static class ReflectionExtensions
+ {
+ public static CustomAttributeData GetCustomAttributeData(this MemberInfo memberInfo, Type type)
+ {
+ return memberInfo.CustomAttributes.FirstOrDefault(a => type.IsAssignableFrom(a.AttributeType));
+ }
+
+ public static TValue GetConstructorArgument(this CustomAttributeData customAttributeData, int index)
+ {
+ return index < customAttributeData.ConstructorArguments.Count ? (TValue)customAttributeData.ConstructorArguments[index].Value! : default!;
+ }
+
+ public static bool IsInitOnly(this MethodInfo method)
+ {
+ MethodInfoWrapper? methodInfoWrapper = method as MethodInfoWrapper;
+
+ if (methodInfoWrapper == null)
+ {
+ throw new ArgumentException("Expected a MethodInfoWrapper instance.", nameof(method));
+ }
+
+ return methodInfoWrapper.IsInitOnly;
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs
new file mode 100644
index 00000000000000..610e7fe4eef6aa
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs
@@ -0,0 +1,34 @@
+// 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.Reflection;
+using Microsoft.CodeAnalysis;
+
+namespace System.Text.Json.SourceGeneration.Reflection
+{
+ internal static class RoslynExtensions
+ {
+ public static Type AsType(this ITypeSymbol typeSymbol, MetadataLoadContextInternal metadataLoadContext)
+ {
+ if (typeSymbol == null)
+ {
+ return null;
+ }
+
+ return new TypeWrapper(typeSymbol, metadataLoadContext);
+ }
+
+ public static MethodInfo AsMethodInfo(this IMethodSymbol methodSymbol, MetadataLoadContextInternal metadataLoadContext) => (methodSymbol == null ? null : new MethodInfoWrapper(methodSymbol, metadataLoadContext))!;
+
+ public static IEnumerable BaseTypes(this INamedTypeSymbol typeSymbol)
+ {
+ var t = typeSymbol;
+ while (t != null)
+ {
+ yield return t;
+ t = t.BaseType;
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs
new file mode 100644
index 00000000000000..4bfb71a9ce8717
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs
@@ -0,0 +1,77 @@
+// 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.Linq;
+
+namespace System.Text.Json.SourceGeneration.Reflection
+{
+ internal static class TypeExtensions
+ {
+ public static string GetUniqueCompilableTypeName(this Type type) => GetCompilableTypeName(type, type.FullName);
+
+ public static string GetCompilableTypeName(this Type type) => GetCompilableTypeName(type, type.Name);
+
+ private static string GetCompilableTypeName(Type type, string name)
+ {
+ if (!type.IsGenericType)
+ {
+ return name.Replace('+', '.');
+ }
+
+ // TODO: Guard upstream against open generics.
+ Debug.Assert(!type.ContainsGenericParameters);
+
+ int backTickIndex = name.IndexOf('`');
+ string baseName = name.Substring(0, backTickIndex).Replace('+', '.');
+
+ return $"{baseName}<{string.Join(",", type.GetGenericArguments().Select(arg => GetUniqueCompilableTypeName(arg)))}>";
+ }
+
+ public static string GetFriendlyTypeName(this Type type)
+ {
+ return GetFriendlyTypeName(type.GetCompilableTypeName());
+ }
+
+ private static string GetFriendlyTypeName(string compilableName)
+ {
+ return compilableName.Replace(".", "").Replace("<", "").Replace(">", "").Replace(",", "").Replace("[]", "Array");
+ }
+
+ public static Type NullableOfTType { get; set; }
+
+ public static bool IsNullableValueType(this Type type, out Type? underlyingType)
+ {
+ 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)
+ {
+ underlyingType = type.GetGenericArguments()[0];
+ return true;
+ }
+
+ underlyingType = null;
+ return false;
+ }
+
+ public static Type? GetCompatibleBaseClass(this Type type, string baseTypeFullName)
+ {
+ Type? baseTypeToCheck = type;
+
+ while (baseTypeToCheck != null && baseTypeToCheck != typeof(object))
+ {
+ if (baseTypeToCheck.FullName == baseTypeFullName)
+ {
+ return baseTypeToCheck;
+ }
+
+ baseTypeToCheck = baseTypeToCheck.BaseType;
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs
new file mode 100644
index 00000000000000..2ffc4725922ab6
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs
@@ -0,0 +1,490 @@
+// 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.Collections.Immutable;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using Microsoft.CodeAnalysis;
+
+namespace System.Text.Json.SourceGeneration.Reflection
+{
+ internal class TypeWrapper : Type
+ {
+ private readonly ITypeSymbol _typeSymbol;
+
+ private readonly MetadataLoadContextInternal _metadataLoadContext;
+
+ private INamedTypeSymbol? _namedTypeSymbol;
+
+ private IArrayTypeSymbol? _arrayTypeSymbol;
+
+ private Type _elementType;
+
+ public TypeWrapper(ITypeSymbol namedTypeSymbol, MetadataLoadContextInternal metadataLoadContext)
+ {
+ _typeSymbol = namedTypeSymbol;
+ _metadataLoadContext = metadataLoadContext;
+ _namedTypeSymbol = _typeSymbol as INamedTypeSymbol;
+ _arrayTypeSymbol = _typeSymbol as IArrayTypeSymbol;
+ }
+
+ public override Assembly Assembly => new AssemblyWrapper(_typeSymbol.ContainingAssembly, _metadataLoadContext);
+
+ private string? _assemblyQualifiedName;
+
+ public override string AssemblyQualifiedName
+ {
+ get
+ {
+ if (_assemblyQualifiedName == null)
+ {
+ StringBuilder sb = new();
+
+ AssemblyIdentity identity = _typeSymbol.ContainingAssembly.Identity;
+
+ sb.Append(FullName);
+
+ sb.Append(", ");
+ sb.Append(identity.Name);
+
+ sb.Append(", Version=");
+ sb.Append(identity.Version);
+
+ if (string.IsNullOrWhiteSpace(identity.CultureName))
+ {
+ sb.Append(", Culture=neutral");
+ }
+
+ sb.Append(", PublicKeyToken=");
+ ImmutableArray publicKeyToken = identity.PublicKeyToken;
+ if (publicKeyToken.Length > 0)
+ {
+ foreach (byte b in publicKeyToken)
+ {
+ sb.Append(b.ToString("x2"));
+ }
+ }
+ else
+ {
+ sb.Append("null");
+ }
+
+ _assemblyQualifiedName = sb.ToString();
+ }
+
+ return _assemblyQualifiedName;
+ }
+ }
+
+ public override Type BaseType => _typeSymbol.BaseType!.AsType(_metadataLoadContext);
+
+ private string? _fullName;
+
+ public override string FullName
+ {
+ get
+ {
+ if (_fullName == null)
+ {
+ StringBuilder sb = new();
+
+ if (this.IsNullableValueType(out Type? underlyingType))
+ {
+ sb.Append("System.Nullable`1[[");
+ sb.Append(underlyingType.AssemblyQualifiedName);
+ sb.Append("]]");
+ }
+ else
+ {
+ sb.Append(Name);
+
+ for (ISymbol currentSymbol = _typeSymbol.ContainingSymbol; currentSymbol != null && currentSymbol.Kind != SymbolKind.Namespace; currentSymbol = currentSymbol.ContainingSymbol)
+ {
+ sb.Insert(0, $"{currentSymbol.Name}+");
+ }
+
+ if (!string.IsNullOrWhiteSpace(Namespace))
+ {
+ sb.Insert(0, $"{Namespace}.");
+ }
+ }
+
+ _fullName = sb.ToString();
+ }
+
+ return _fullName;
+ }
+ }
+
+ public override Guid GUID => Guid.Empty;
+
+ public override Module Module => throw new NotImplementedException();
+
+ public override string Namespace =>
+ IsArray ?
+ GetElementType().Namespace :
+ _typeSymbol.ContainingNamespace?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining))!;
+
+ public override Type UnderlyingSystemType => this;
+
+ public override string Name
+ {
+ get
+ {
+ if (_arrayTypeSymbol == null)
+ {
+ return _typeSymbol.MetadataName;
+ }
+
+ Type elementType = GetElementType();
+ return elementType.Name + "[]";
+ }
+ }
+
+ private Type _enumType;
+
+ public override bool IsEnum
+ {
+ get
+ {
+ _enumType ??= _metadataLoadContext.Resolve(typeof(Enum));
+ return IsSubclassOf(_enumType);
+ }
+ }
+
+ public override bool IsGenericType => _namedTypeSymbol?.IsGenericType == true;
+
+ public override bool ContainsGenericParameters => _namedTypeSymbol?.IsUnboundGenericType == true;
+
+ public override bool IsGenericTypeDefinition => base.IsGenericTypeDefinition;
+
+ public INamespaceSymbol GetNamespaceSymbol => _typeSymbol.ContainingNamespace;
+
+ public override Type[] GetGenericArguments()
+ {
+ var args = new List();
+ foreach (ITypeSymbol item in _namedTypeSymbol.TypeArguments)
+ {
+ args.Add(item.AsType(_metadataLoadContext));
+ }
+ return args.ToArray();
+ }
+
+ public override Type GetGenericTypeDefinition()
+ {
+ return _namedTypeSymbol.ConstructedFrom.AsType(_metadataLoadContext);
+ }
+
+ public override IList GetCustomAttributesData()
+ {
+ var attributes = new List();
+ foreach (AttributeData a in _typeSymbol.GetAttributes())
+ {
+ attributes.Add(new CustomAttributeDataWrapper(a, _metadataLoadContext));
+ }
+ return attributes;
+ }
+
+ public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr)
+ {
+ var ctors = new List();
+ foreach (IMethodSymbol c in _namedTypeSymbol.Constructors)
+ {
+ ctors.Add(new ConstructorInfoWrapper(c, _metadataLoadContext));
+ }
+ return ctors.ToArray();
+ }
+
+ public override object[] GetCustomAttributes(bool inherit)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override object[] GetCustomAttributes(Type attributeType, bool inherit)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override Type GetElementType()
+ {
+ _elementType ??= _arrayTypeSymbol?.ElementType.AsType(_metadataLoadContext)!;
+ return _elementType;
+ }
+
+ public override EventInfo GetEvent(string name, BindingFlags bindingAttr)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override EventInfo[] GetEvents(BindingFlags bindingAttr)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override FieldInfo GetField(string name, BindingFlags bindingAttr)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override FieldInfo[] GetFields(BindingFlags bindingAttr)
+ {
+ var fields = new List();
+ foreach (ISymbol item in _typeSymbol.GetMembers())
+ {
+ // Associated Symbol checks the field is not a backingfield.
+ if (item is IFieldSymbol field && field.AssociatedSymbol == null && !field.IsReadOnly)
+ {
+ if ((item.DeclaredAccessibility & Accessibility.Public) == Accessibility.Public)
+ {
+ fields.Add(new FieldInfoWrapper(field, _metadataLoadContext));
+ }
+ }
+ }
+ return fields.ToArray();
+ }
+
+ public override Type GetInterface(string name, bool ignoreCase)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override Type[] GetInterfaces()
+ {
+ var interfaces = new List();
+ foreach (INamedTypeSymbol i in _typeSymbol.Interfaces)
+ {
+ interfaces.Add(i.AsType(_metadataLoadContext));
+ }
+ return interfaces.ToArray();
+ }
+
+ public override MemberInfo[] GetMembers(BindingFlags bindingAttr)
+ {
+ var members = new List();
+ foreach (ISymbol m in _typeSymbol.GetMembers())
+ {
+ members.Add(new MemberInfoWrapper(m, _metadataLoadContext));
+ }
+ return members.ToArray();
+ }
+
+ public override MethodInfo[] GetMethods(BindingFlags bindingAttr)
+ {
+ var methods = new List();
+ foreach (ISymbol m in _typeSymbol.GetMembers())
+ {
+ // TODO: Efficiency
+ if (m is IMethodSymbol method && !_namedTypeSymbol.Constructors.Contains(method))
+ {
+ methods.Add(method.AsMethodInfo(_metadataLoadContext));
+ }
+ }
+ return methods.ToArray();
+ }
+
+ public override Type GetNestedType(string name, BindingFlags bindingAttr)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override Type[] GetNestedTypes(BindingFlags bindingAttr)
+ {
+ var nestedTypes = new List();
+ foreach (INamedTypeSymbol type in _typeSymbol.GetTypeMembers())
+ {
+ nestedTypes.Add(type.AsType(_metadataLoadContext));
+ }
+ return nestedTypes.ToArray();
+ }
+
+ // TODO: make sure to use bindingAttr for correctness. Current implementation assumes public and non-static.
+ public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
+ {
+ var properties = new List();
+
+ foreach (ISymbol item in _typeSymbol.GetMembers())
+ {
+ if (item is IPropertySymbol property)
+ {
+ if ((item.DeclaredAccessibility & Accessibility.Public) == Accessibility.Public)
+ {
+ properties.Add(new PropertyInfoWrapper(property, _metadataLoadContext));
+ }
+ }
+ }
+
+ return properties.ToArray();
+ }
+
+ public override object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override bool IsDefined(Type attributeType, bool inherit)
+ {
+ throw new NotImplementedException();
+ }
+
+ private TypeAttributes? _typeAttributes;
+
+ protected override TypeAttributes GetAttributeFlagsImpl()
+ {
+ if (!_typeAttributes.HasValue)
+ {
+ _typeAttributes = default(TypeAttributes);
+
+ if (_typeSymbol.IsAbstract)
+ {
+ _typeAttributes |= TypeAttributes.Abstract;
+ }
+
+ if (_typeSymbol.TypeKind == TypeKind.Interface)
+ {
+ _typeAttributes |= TypeAttributes.Interface;
+ }
+
+ if (_typeSymbol.ContainingType != null && _typeSymbol.DeclaredAccessibility == Accessibility.Private)
+ {
+ _typeAttributes |= TypeAttributes.NestedPrivate;
+ }
+ }
+
+ return _typeAttributes.Value;
+ }
+
+ protected override ConstructorInfo GetConstructorImpl(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers)
+ {
+ foreach (ConstructorInfo constructor in GetConstructors(bindingAttr))
+ {
+ ParameterInfo[] parameters = constructor.GetParameters();
+
+ if (parameters.Length == types.Length)
+ {
+ bool mismatched = false;
+ for (int i = 0; i < parameters.Length; i++)
+ {
+ if (parameters[i].ParameterType != types[i])
+ {
+ mismatched = true;
+ break;
+ }
+ }
+
+ if (!mismatched)
+ {
+ return constructor;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ protected override MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers)
+ {
+ // TODO: peformance; caching; honor bindingAttr
+ foreach (PropertyInfo propertyInfo in GetProperties(bindingAttr))
+ {
+ if (propertyInfo.Name == name)
+ {
+ return propertyInfo;
+ }
+ }
+
+ return null!;
+ }
+
+ protected override bool HasElementTypeImpl()
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override bool IsArrayImpl()
+ {
+ return _arrayTypeSymbol != null;
+ }
+
+ private Type _valueType;
+
+ protected override bool IsValueTypeImpl()
+ {
+ _valueType ??= _metadataLoadContext.Resolve(typeof(ValueType));
+ return IsSubclassOf(_valueType);
+ }
+
+ protected override bool IsByRefImpl()
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override bool IsCOMObjectImpl()
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override bool IsPointerImpl()
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override bool IsPrimitiveImpl()
+ {
+ throw new NotImplementedException();
+ }
+
+ public override bool IsAssignableFrom(Type c)
+ {
+ if (c is TypeWrapper tr)
+ {
+ return tr._typeSymbol.AllInterfaces.Contains(_typeSymbol, SymbolEqualityComparer.Default) ||
+ (tr._namedTypeSymbol != null && tr._namedTypeSymbol.BaseTypes().Contains(_typeSymbol, SymbolEqualityComparer.Default));
+ }
+ else if (_metadataLoadContext.Resolve(c) is TypeWrapper trr)
+ {
+ return trr._typeSymbol.AllInterfaces.Contains(_typeSymbol, SymbolEqualityComparer.Default) ||
+ (trr._namedTypeSymbol != null && trr._namedTypeSymbol.BaseTypes().Contains(_typeSymbol, SymbolEqualityComparer.Default));
+ }
+ return false;
+ }
+
+#pragma warning disable RS1024 // Compare symbols correctly
+ public override int GetHashCode() => _typeSymbol.GetHashCode();
+#pragma warning restore RS1024 // Compare symbols correctly
+
+ public override bool Equals(object o)
+ {
+ if (o is TypeWrapper tw)
+ {
+ return _typeSymbol.Equals(tw._typeSymbol, SymbolEqualityComparer.Default);
+ }
+ else if (o is Type t && _metadataLoadContext.Resolve(t) is TypeWrapper tww)
+ {
+ return _typeSymbol.Equals(tww._typeSymbol, SymbolEqualityComparer.Default);
+ }
+
+ return base.Equals(o);
+ }
+
+ public override bool Equals(Type o)
+ {
+ if (o is TypeWrapper tw)
+ {
+ return _typeSymbol.Equals(tw._typeSymbol, SymbolEqualityComparer.Default);
+ }
+ else if (_metadataLoadContext.Resolve(o) is TypeWrapper tww)
+ {
+ return _typeSymbol.Equals(tww._typeSymbol, SymbolEqualityComparer.Default);
+ }
+ return base.Equals(o);
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj
new file mode 100644
index 00000000000000..92e70583da6722
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj
@@ -0,0 +1,48 @@
+
+
+ netstandard2.0
+ false
+ enable
+
+ CS1574
+
+
+
+ $(DefineConstants);BUILDING_SOURCE_GENERATOR
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/libraries/System.Text.Json/gen/TypeMetadata.cs b/src/libraries/System.Text.Json/gen/TypeMetadata.cs
new file mode 100644
index 00000000000000..7f9ea6385f2150
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/TypeMetadata.cs
@@ -0,0 +1,82 @@
+// 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? 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? 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;
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/pkg/System.Text.Json.pkgproj b/src/libraries/System.Text.Json/pkg/System.Text.Json.pkgproj
index c02e237aea1ea3..c714baa66768a7 100644
--- a/src/libraries/System.Text.Json/pkg/System.Text.Json.pkgproj
+++ b/src/libraries/System.Text.Json/pkg/System.Text.Json.pkgproj
@@ -5,6 +5,7 @@
net461;netcoreapp2.0;uap10.0.16299;$(AllXamarinFrameworks)
+