From 1ea4ec249fb14f66c779ad24047d004b6f3cd912 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 14 Apr 2026 01:45:55 +0000
Subject: [PATCH 1/3] Add parameterless [JsonSerializable] attribute and POCO
source generation pipeline
- Extended JsonSerializableAttribute with parameterless ctor and AttributeTargets.Struct
- Updated ref assembly
- Added SYSLIB1227 (type must be partial) and SYSLIB1228 (member name collision) diagnostics
- Added PocoTypeGenerationSpec model
- Added second pipeline in Initialize() for POCO types
- Added EmitPocoType() to generate backing context and static JsonTypeInfo property
- Updated Roslyn 3.11 generator with equivalent POCO support
- Handled parameterless [JsonSerializable] in ParseJsonSerializableAttribute
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/d115f32b-7c2c-4cca-a15d-d57ce9850f97
Co-authored-by: agocke <515774+agocke@users.noreply.github.com>
---
docs/project/list-of-diagnostics.md | 4 +-
.../Common/JsonSerializableAttribute.cs | 17 +-
...onSourceGenerator.DiagnosticDescriptors.cs | 16 ++
.../gen/JsonSourceGenerator.Emitter.cs | 81 ++++++++++
.../gen/JsonSourceGenerator.Parser.cs | 147 +++++++++++++++++-
.../gen/JsonSourceGenerator.Roslyn3.11.cs | 132 +++++++++++-----
.../gen/JsonSourceGenerator.Roslyn4.0.cs | 64 ++++++++
.../gen/Model/PocoTypeGenerationSpec.cs | 60 +++++++
.../gen/Resources/Strings.resx | 12 ++
.../gen/Resources/xlf/Strings.cs.xlf | 20 +++
.../gen/Resources/xlf/Strings.de.xlf | 20 +++
.../gen/Resources/xlf/Strings.es.xlf | 20 +++
.../gen/Resources/xlf/Strings.fr.xlf | 20 +++
.../gen/Resources/xlf/Strings.it.xlf | 20 +++
.../gen/Resources/xlf/Strings.ja.xlf | 20 +++
.../gen/Resources/xlf/Strings.ko.xlf | 20 +++
.../gen/Resources/xlf/Strings.pl.xlf | 20 +++
.../gen/Resources/xlf/Strings.pt-BR.xlf | 20 +++
.../gen/Resources/xlf/Strings.ru.xlf | 20 +++
.../gen/Resources/xlf/Strings.tr.xlf | 20 +++
.../gen/Resources/xlf/Strings.zh-Hans.xlf | 20 +++
.../gen/Resources/xlf/Strings.zh-Hant.xlf | 20 +++
.../System.Text.Json.SourceGeneration.targets | 1 +
.../System.Text.Json/ref/System.Text.Json.cs | 3 +-
24 files changed, 748 insertions(+), 49 deletions(-)
create mode 100644 src/libraries/System.Text.Json/gen/Model/PocoTypeGenerationSpec.cs
diff --git a/docs/project/list-of-diagnostics.md b/docs/project/list-of-diagnostics.md
index 306e9f9d8f2e83..ca4af89e232f27 100644
--- a/docs/project/list-of-diagnostics.md
+++ b/docs/project/list-of-diagnostics.md
@@ -272,8 +272,8 @@ The diagnostic id values reserved for .NET Libraries analyzer warnings are `SYSL
| __`SYSLIB1224`__ | Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. |
| __`SYSLIB1225`__ | Type includes ref like property, field or constructor parameter. |
| __`SYSLIB1226`__ | 'JsonIgnoreCondition.Always' is not valid on type-level 'JsonIgnoreAttribute' annotations. |
-| __`SYSLIB1227`__ | _`SYSLIB1220`-`SYSLIB1229` reserved for System.Text.Json.SourceGeneration._ |
-| __`SYSLIB1228`__ | _`SYSLIB1220`-`SYSLIB1229` reserved for System.Text.Json.SourceGeneration._ |
+| __`SYSLIB1227`__ | Types annotated with the parameterless JsonSerializableAttribute must be partial. |
+| __`SYSLIB1228`__ | Type already contains a member named 'JsonTypeInfo'. |
| __`SYSLIB1229`__ | _`SYSLIB1220`-`SYSLIB1229` reserved for System.Text.Json.SourceGeneration._ |
| __`SYSLIB1230`__ | Deriving from a `GeneratedComInterface`-attributed interface defined in another assembly is not supported. |
| __`SYSLIB1231`__ | _`SYSLIB1230`-`SYSLIB1239` reserved for Microsoft.Interop.ComInterfaceGenerator._ |
diff --git a/src/libraries/System.Text.Json/Common/JsonSerializableAttribute.cs b/src/libraries/System.Text.Json/Common/JsonSerializableAttribute.cs
index 6b2535269dc527..4b77451ce714d2 100644
--- a/src/libraries/System.Text.Json/Common/JsonSerializableAttribute.cs
+++ b/src/libraries/System.Text.Json/Common/JsonSerializableAttribute.cs
@@ -11,7 +11,12 @@ namespace System.Text.Json.Serialization
/// Instructs the System.Text.Json source generator to generate source code to help optimize performance
/// when serializing and deserializing instances of the specified type and types in its object graph.
///
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+ ///
+ /// This attribute can be applied to a -derived class to register types
+ /// for source generation, or directly to a partial class or struct to enable simplified source generation
+ /// with an auto-generated context and a static JsonTypeInfo property.
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)]
#if BUILDING_SOURCE_GENERATOR
internal
@@ -28,6 +33,16 @@ sealed class JsonSerializableAttribute : JsonAttribute
public JsonSerializableAttribute(Type type) { }
#pragma warning restore IDE0060
+ ///
+ /// Initializes a new instance of when applied directly to the type to be serialized.
+ ///
+ ///
+ /// When this parameterless constructor is used on a partial class or struct, the source generator will
+ /// automatically generate a backing and a static JsonTypeInfo
+ /// property on the annotated type.
+ ///
+ public JsonSerializableAttribute() { }
+
///
/// The name of the property for the generated for
/// the type on the generated, derived type.
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs
index a926b75a5c1631..108126f2a1329e 100644
--- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs
@@ -131,6 +131,22 @@ internal static class DiagnosticDescriptors
category: JsonConstants.SystemTextJsonSourceGenerationName,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor JsonSerializableTypeMustBePartial { get; } = DiagnosticDescriptorHelper.Create(
+ id: "SYSLIB1227",
+ title: new LocalizableResourceString(nameof(SR.JsonSerializableTypeMustBePartialTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.JsonSerializableTypeMustBePartialMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
+ category: JsonConstants.SystemTextJsonSourceGenerationName,
+ defaultSeverity: DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor JsonSerializableTypeHasJsonTypeInfoMember { get; } = DiagnosticDescriptorHelper.Create(
+ id: "SYSLIB1228",
+ title: new LocalizableResourceString(nameof(SR.JsonSerializableTypeHasJsonTypeInfoMemberTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.JsonSerializableTypeHasJsonTypeInfoMemberMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
+ category: JsonConstants.SystemTextJsonSourceGenerationName,
+ defaultSeverity: DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
}
}
}
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
index d30851a469dc1b..fbb00130201a1e 100644
--- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
@@ -145,6 +145,87 @@ public void Emit(ContextGenerationSpec contextGenerationSpec)
_typeIndex.Clear();
}
+ ///
+ /// Emits source code for a type annotated with the parameterless [JsonSerializable] attribute.
+ /// Generates a file-scoped backing JsonSerializerContext and a partial type extension
+ /// with a static JsonTypeInfo property.
+ ///
+ public void EmitPocoType(PocoTypeGenerationSpec pocoSpec)
+ {
+ var writer = new SourceWriter();
+
+ writer.WriteLine("""
+ //
+
+ #nullable enable annotations
+ #nullable disable warnings
+
+ // Suppress warnings about [Obsolete] member usage in generated code.
+ #pragma warning disable CS0612, CS0618
+
+ """);
+
+ if (pocoSpec.Namespace != null)
+ {
+ writer.WriteLine($"namespace {pocoSpec.Namespace}");
+ writer.WriteLine('{');
+ writer.Indentation++;
+ }
+
+ // Emit containing type declarations
+ ImmutableEquatableArray typeDeclarations = pocoSpec.TypeDeclarations;
+ Debug.Assert(typeDeclarations.Count > 0);
+
+ for (int i = typeDeclarations.Count - 1; i > 0; i--)
+ {
+ writer.WriteLine(typeDeclarations[i]);
+ writer.WriteLine('{');
+ writer.Indentation++;
+ }
+
+ // Emit the partial type declaration with the static property
+ writer.WriteLine($"""[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{s_assemblyName.Name}", "{s_assemblyName.Version}")]""");
+ writer.WriteLine(typeDeclarations[0]);
+ writer.WriteLine('{');
+ writer.Indentation++;
+
+ string fullyQualifiedTypeName = pocoSpec.TypeRef.FullyQualifiedName;
+
+ writer.WriteLine($$"""
+ ///
+ /// Gets the generated for .
+ ///
+ public static global::System.Text.Json.Serialization.Metadata.JsonTypeInfo<{{fullyQualifiedTypeName}}> {{pocoSpec.TypeInfoPropertyName}} =>
+ {{pocoSpec.GeneratedContextName}}.Default.{{pocoSpec.TypeName}};
+ """);
+
+ writer.Indentation--;
+ writer.WriteLine('}');
+
+ // Close containing type declarations
+ for (int i = 1; i < typeDeclarations.Count; i++)
+ {
+ writer.Indentation--;
+ writer.WriteLine('}');
+ }
+
+ // Emit the file-scoped backing context class
+ writer.WriteLine();
+ writer.WriteLine($"""[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{s_assemblyName.Name}", "{s_assemblyName.Version}")]""");
+ writer.WriteLine($"[global::System.Text.Json.Serialization.JsonSerializable(typeof({fullyQualifiedTypeName}))]");
+ writer.WriteLine($"file sealed partial class {pocoSpec.GeneratedContextName} : global::System.Text.Json.Serialization.JsonSerializerContext");
+ writer.WriteLine('{');
+ writer.WriteLine('}');
+
+ if (pocoSpec.Namespace != null)
+ {
+ writer.Indentation--;
+ writer.WriteLine('}');
+ }
+
+ AddSource($"{pocoSpec.TypeName}.JsonSerializable.g.cs", writer.ToSourceText());
+ }
+
private static SourceWriter CreateSourceWriterWithContextHeader(ContextGenerationSpec contextSpec, bool isPrimaryContextSourceFile = false, string? interfaceImplementation = null)
{
var writer = new SourceWriter();
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
index 5428245c7ce1b1..c3f85d4a8c6b25 100644
--- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
@@ -106,7 +106,14 @@ public Parser(KnownTypeSymbols knownSymbols)
if (!_knownSymbols.JsonSerializerContextType.IsAssignableFrom(contextTypeSymbol))
{
- ReportDiagnostic(DiagnosticDescriptors.JsonSerializableAttributeOnNonContextType, _contextClassLocation, contextTypeSymbol.ToDisplayString());
+ // Only emit SYSLIB1224 if the type has [JsonSerializable(typeof(T))] attributes
+ // (i.e., the old-style usage on a non-context type). Parameterless [JsonSerializable]
+ // on a non-context type is handled by the POCO pipeline.
+ if (!HasOnlyParameterlessJsonSerializableAttributes(contextTypeSymbol))
+ {
+ ReportDiagnostic(DiagnosticDescriptors.JsonSerializableAttributeOnNonContextType, _contextClassLocation, contextTypeSymbol.ToDisplayString());
+ }
+
return null;
}
@@ -184,7 +191,137 @@ public Parser(KnownTypeSymbols knownSymbols)
return contextGenSpec;
}
- private static bool TryGetNestedTypeDeclarations(ClassDeclarationSyntax contextClassSyntax, SemanticModel semanticModel, CancellationToken cancellationToken, [NotNullWhen(true)] out List? typeDeclarations)
+ ///
+ /// Checks whether all [JsonSerializable] attributes on the type are parameterless (POCO-style).
+ ///
+ private bool HasOnlyParameterlessJsonSerializableAttributes(INamedTypeSymbol typeSymbol)
+ {
+ Debug.Assert(_knownSymbols.JsonSerializableAttributeType != null);
+
+ foreach (AttributeData attributeData in typeSymbol.GetAttributes())
+ {
+ if (SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, _knownSymbols.JsonSerializableAttributeType))
+ {
+ if (attributeData.ConstructorArguments.Length > 0)
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Parses a type declaration annotated with the parameterless [JsonSerializable] attribute.
+ ///
+ public PocoTypeGenerationSpec? ParsePocoTypeGenerationSpec(TypeDeclarationSyntax typeDeclaration, SemanticModel semanticModel, CancellationToken cancellationToken)
+ {
+ if (!_compilationContainsCoreJsonTypes)
+ {
+ return null;
+ }
+
+ Debug.Assert(_knownSymbols.JsonSerializableAttributeType != null);
+
+ INamedTypeSymbol? typeSymbol = semanticModel.GetDeclaredSymbol(typeDeclaration, cancellationToken);
+ if (typeSymbol is null)
+ {
+ return null;
+ }
+
+ Location? location = typeSymbol.GetLocation();
+
+ // Skip if this type derives from JsonSerializerContext — that's handled by the existing pipeline
+ if (_knownSymbols.JsonSerializerContextType != null &&
+ _knownSymbols.JsonSerializerContextType.IsAssignableFrom(typeSymbol))
+ {
+ return null;
+ }
+
+ // Find the parameterless [JsonSerializable] attribute
+ AttributeData? parameterlessAttribute = null;
+ foreach (AttributeData attributeData in typeSymbol.GetAttributes())
+ {
+ if (SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, _knownSymbols.JsonSerializableAttributeType) &&
+ attributeData.ConstructorArguments.Length == 0)
+ {
+ parameterlessAttribute = attributeData;
+ break;
+ }
+ }
+
+ if (parameterlessAttribute is null)
+ {
+ return null;
+ }
+
+ // Validate language version
+ LanguageVersion? langVersion = _knownSymbols.Compilation.GetLanguageVersion();
+ if (langVersion is null or < MinimumSupportedLanguageVersion)
+ {
+ Diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.JsonUnsupportedLanguageVersion,
+ location, langVersion?.ToDisplayString(), MinimumSupportedLanguageVersion.ToDisplayString()));
+ return null;
+ }
+
+ // Validate that the type and all containing types are partial
+ if (!TryGetNestedTypeDeclarations(typeDeclaration, semanticModel, cancellationToken, out List? typeDeclarations))
+ {
+ Diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.JsonSerializableTypeMustBePartial,
+ location, typeSymbol.ToDisplayString()));
+ return null;
+ }
+
+ // Parse named arguments from the attribute
+ string? typeInfoPropertyName = null;
+ JsonSourceGenerationMode? generationMode = null;
+
+ foreach (KeyValuePair namedArg in parameterlessAttribute.NamedArguments)
+ {
+ switch (namedArg.Key)
+ {
+ case nameof(JsonSerializableAttribute.TypeInfoPropertyName):
+ typeInfoPropertyName = (string)namedArg.Value.Value!;
+ break;
+ case nameof(JsonSerializableAttribute.GenerationMode):
+ generationMode = (JsonSourceGenerationMode)namedArg.Value.Value!;
+ break;
+ }
+ }
+
+ // Default property name is "JsonTypeInfo"
+ string propertyName = typeInfoPropertyName ?? "JsonTypeInfo";
+
+ // Check for member name collision
+ foreach (ISymbol member in typeSymbol.GetMembers(propertyName))
+ {
+ // Only report collision if the member is not compiler-generated (i.e., not from our previous generation)
+ if (!member.IsImplicitlyDeclared)
+ {
+ Diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.JsonSerializableTypeHasJsonTypeInfoMember,
+ location, typeSymbol.ToDisplayString()));
+ return null;
+ }
+ }
+
+ string typeName = typeSymbol.Name;
+ string generatedContextName = $"__JsonContext_{typeName}";
+
+ return new PocoTypeGenerationSpec
+ {
+ TypeRef = new(typeSymbol),
+ TypeName = typeName,
+ TypeInfoPropertyName = propertyName,
+ Namespace = typeSymbol.ContainingNamespace is { IsGlobalNamespace: false } ns ? ns.ToDisplayString() : null,
+ TypeDeclarations = typeDeclarations.ToImmutableEquatableArray(),
+ GeneratedContextName = generatedContextName,
+ GenerationMode = generationMode,
+ IsValueType = typeSymbol.IsValueType,
+ };
+ }
+
+ private static bool TryGetNestedTypeDeclarations(TypeDeclarationSyntax contextClassSyntax, SemanticModel semanticModel, CancellationToken cancellationToken, [NotNullWhen(true)] out List? typeDeclarations)
{
typeDeclarations = null;
@@ -523,6 +660,12 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN
private TypeToGenerate? ParseJsonSerializableAttribute(AttributeData attributeData)
{
+ // Skip parameterless [JsonSerializable] attributes (POCO-style) — they're handled by the POCO pipeline.
+ if (attributeData.ConstructorArguments.Length == 0)
+ {
+ return null;
+ }
+
Debug.Assert(attributeData.ConstructorArguments.Length == 1);
var typeSymbol = (ITypeSymbol?)attributeData.ConstructorArguments[0].Value;
if (typeSymbol is null)
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs
index 5397715ede9da8..934a94c40e8e91 100644
--- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs
@@ -52,45 +52,68 @@ public void Execute(GeneratorExecutionContext executionContext)
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
try
{
- if (executionContext.SyntaxContextReceiver is not SyntaxContextReceiver receiver || receiver.ContextClassDeclarations == null)
+ if (executionContext.SyntaxContextReceiver is not SyntaxContextReceiver receiver)
{
// nothing to do yet
return;
}
- // Stage 1. Parse the identified JsonSerializerContext classes and store the model types.
KnownTypeSymbols knownSymbols = new(executionContext.Compilation);
- Parser parser = new(knownSymbols);
- List? contextGenerationSpecs = null;
- foreach ((ClassDeclarationSyntax? contextClassDeclaration, SemanticModel semanticModel) in receiver.ContextClassDeclarations)
+ // Stage 1a. Parse the identified JsonSerializerContext classes and store the model types.
+ if (receiver.ContextClassDeclarations != null)
{
- ContextGenerationSpec? contextGenerationSpec = parser.ParseContextGenerationSpec(contextClassDeclaration, semanticModel, executionContext.CancellationToken);
- if (contextGenerationSpec is null)
+ Parser parser = new(knownSymbols);
+
+ List? contextGenerationSpecs = null;
+ foreach ((ClassDeclarationSyntax? contextClassDeclaration, SemanticModel semanticModel) in receiver.ContextClassDeclarations)
{
- continue;
+ ContextGenerationSpec? contextGenerationSpec = parser.ParseContextGenerationSpec(contextClassDeclaration, semanticModel, executionContext.CancellationToken);
+ if (contextGenerationSpec is null)
+ {
+ continue;
+ }
+
+ (contextGenerationSpecs ??= new()).Add(contextGenerationSpec);
}
- (contextGenerationSpecs ??= new()).Add(contextGenerationSpec);
- }
+ // Report any diagnostics gathered by the parser.
+ foreach (Diagnostic diagnostic in parser.Diagnostics)
+ {
+ executionContext.ReportDiagnostic(diagnostic);
+ }
- // Stage 2. Report any diagnostics gathered by the parser.
- foreach (Diagnostic diagnostic in parser.Diagnostics)
- {
- executionContext.ReportDiagnostic(diagnostic);
+ if (contextGenerationSpecs is not null)
+ {
+ // Emit source code from the spec models.
+ OnSourceEmitting?.Invoke(contextGenerationSpecs.ToImmutableArray());
+ Emitter emitter = new(executionContext);
+ foreach (ContextGenerationSpec contextGenerationSpec in contextGenerationSpecs)
+ {
+ emitter.Emit(contextGenerationSpec);
+ }
+ }
}
- if (contextGenerationSpecs is null)
+ // Stage 1b. Parse POCO types annotated with parameterless [JsonSerializable].
+ if (receiver.PocoTypeDeclarations != null)
{
- return;
- }
+ Parser parser = new(knownSymbols);
- // Stage 3. Emit source code from the spec models.
- OnSourceEmitting?.Invoke(contextGenerationSpecs.ToImmutableArray());
- Emitter emitter = new(executionContext);
- foreach (ContextGenerationSpec contextGenerationSpec in contextGenerationSpecs)
- {
- emitter.Emit(contextGenerationSpec);
+ foreach ((TypeDeclarationSyntax typeDeclaration, SemanticModel semanticModel) in receiver.PocoTypeDeclarations)
+ {
+ PocoTypeGenerationSpec? pocoSpec = parser.ParsePocoTypeGenerationSpec(typeDeclaration, semanticModel, executionContext.CancellationToken);
+ if (pocoSpec is not null)
+ {
+ Emitter emitter = new(executionContext);
+ emitter.EmitPocoType(pocoSpec);
+ }
+ }
+
+ foreach (Diagnostic diagnostic in parser.Diagnostics)
+ {
+ executionContext.ReportDiagnostic(diagnostic);
+ }
}
}
finally
@@ -109,48 +132,71 @@ public SyntaxContextReceiver(CancellationToken cancellationToken)
}
public List<(ClassDeclarationSyntax, SemanticModel)>? ContextClassDeclarations { get; private set; }
+ public List<(TypeDeclarationSyntax, SemanticModel)>? PocoTypeDeclarations { get; private set; }
public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
{
- if (IsSyntaxTargetForGeneration(context.Node))
+ if (context.Node is TypeDeclarationSyntax { AttributeLists.Count: > 0 } typeDeclaration)
{
- ClassDeclarationSyntax? classSyntax = GetSemanticTargetForGeneration(context, _cancellationToken);
- if (classSyntax != null)
+ if (typeDeclaration is ClassDeclarationSyntax { BaseList.Types.Count: > 0 } classDeclaration)
+ {
+ // Could be a JsonSerializerContext-derived class
+ if (HasJsonSerializableAttribute(context, classDeclaration))
+ {
+ (ContextClassDeclarations ??= new()).Add((classDeclaration, context.SemanticModel));
+ }
+ }
+
+ // Also check for parameterless [JsonSerializable] on any type (POCO pattern)
+ if (HasParameterlessJsonSerializableAttribute(context, typeDeclaration))
{
- (ContextClassDeclarations ??= new()).Add((classSyntax, context.SemanticModel));
+ (PocoTypeDeclarations ??= new()).Add((typeDeclaration, context.SemanticModel));
}
}
}
- private static bool IsSyntaxTargetForGeneration(SyntaxNode node) => node is ClassDeclarationSyntax { AttributeLists.Count: > 0, BaseList.Types.Count: > 0 };
-
- private static ClassDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context, CancellationToken cancellationToken)
+ private static bool HasJsonSerializableAttribute(GeneratorSyntaxContext context, ClassDeclarationSyntax classDeclaration)
{
- var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
-
- foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntax.AttributeLists)
+ foreach (AttributeListSyntax attributeListSyntax in classDeclaration.AttributeLists)
{
foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes)
{
- cancellationToken.ThrowIfCancellationRequested();
-
- IMethodSymbol? attributeSymbol = context.SemanticModel.GetSymbolInfo(attributeSyntax, cancellationToken).Symbol as IMethodSymbol;
- if (attributeSymbol == null)
+ if (context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol is IMethodSymbol attributeSymbol)
{
- continue;
+ string fullName = attributeSymbol.ContainingType.ToDisplayString();
+ if (fullName == Parser.JsonSerializableAttributeFullName)
+ {
+ return true;
+ }
}
+ }
+ }
- INamedTypeSymbol attributeContainingTypeSymbol = attributeSymbol.ContainingType;
- string fullName = attributeContainingTypeSymbol.ToDisplayString();
+ return false;
+ }
- if (fullName == Parser.JsonSerializableAttributeFullName)
+ private static bool HasParameterlessJsonSerializableAttribute(GeneratorSyntaxContext context, TypeDeclarationSyntax typeDeclaration)
+ {
+ foreach (AttributeListSyntax attributeListSyntax in typeDeclaration.AttributeLists)
+ {
+ foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes)
+ {
+ // Parameterless: no argument list, or empty argument list
+ if (attributeSyntax.ArgumentList is null or { Arguments.Count: 0 })
{
- return classDeclarationSyntax;
+ if (context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol is IMethodSymbol attributeSymbol)
+ {
+ string fullName = attributeSymbol.ContainingType.ToDisplayString();
+ if (fullName == Parser.JsonSerializableAttributeFullName)
+ {
+ return true;
+ }
+ }
}
}
}
- return null;
+ return false;
}
}
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs
index 79fab10a1cfc34..9708ade2da8333 100644
--- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs
@@ -31,6 +31,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
IncrementalValueProvider knownTypeSymbols = context.CompilationProvider
.Select((compilation, _) => new KnownTypeSymbols(compilation));
+ // Pipeline 1: Existing context-based source generation
IncrementalValuesProvider<(ContextGenerationSpec?, ImmutableArray)> contextGenerationSpecs = context.SyntaxProvider
.ForAttributeWithMetadataName(
#if !ROSLYN4_4_OR_GREATER
@@ -88,6 +89,47 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
contextGenerationSpecs.Select(static (t, _) => t.Item2);
context.RegisterSourceOutput(diagnostics, EmitDiagnostics);
+
+ // Pipeline 2: POCO-based source generation (parameterless [JsonSerializable] on data types)
+ IncrementalValuesProvider<(PocoTypeGenerationSpec?, ImmutableArray)> pocoGenerationSpecs = context.SyntaxProvider
+ .ForAttributeWithMetadataName(
+#if !ROSLYN4_4_OR_GREATER
+ context,
+#endif
+ Parser.JsonSerializableAttributeFullName,
+ (node, _) => node is TypeDeclarationSyntax,
+ (context, _) => (TypeDeclaration: (TypeDeclarationSyntax)context.TargetNode, context.SemanticModel))
+ .Combine(knownTypeSymbols)
+ .Select(static (tuple, cancellationToken) =>
+ {
+#pragma warning disable RS1035
+ CultureInfo originalCulture = CultureInfo.CurrentCulture;
+ CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
+ try
+ {
+#pragma warning restore RS1035
+ Parser parser = new(tuple.Right);
+ PocoTypeGenerationSpec? pocoSpec = parser.ParsePocoTypeGenerationSpec(tuple.Left.TypeDeclaration, tuple.Left.SemanticModel, cancellationToken);
+ ImmutableArray pocoDiagnostics = parser.Diagnostics.ToImmutableArray();
+ return (pocoSpec, pocoDiagnostics);
+#pragma warning disable RS1035
+ }
+ finally
+ {
+ CultureInfo.CurrentCulture = originalCulture;
+ }
+#pragma warning restore RS1035
+ });
+
+ IncrementalValuesProvider pocoSpecs =
+ pocoGenerationSpecs.Select(static (t, _) => t.Item1);
+
+ context.RegisterSourceOutput(pocoSpecs, EmitPocoSource);
+
+ IncrementalValuesProvider> pocoDiagnostics =
+ pocoGenerationSpecs.Select(static (t, _) => t.Item2);
+
+ context.RegisterSourceOutput(pocoDiagnostics, EmitDiagnostics);
}
private void EmitSource(SourceProductionContext sourceProductionContext, ContextGenerationSpec? contextGenerationSpec)
@@ -125,6 +167,28 @@ private static void EmitDiagnostics(SourceProductionContext context, ImmutableAr
}
}
+ private static void EmitPocoSource(SourceProductionContext sourceProductionContext, PocoTypeGenerationSpec? pocoSpec)
+ {
+ if (pocoSpec is null)
+ {
+ return;
+ }
+
+#pragma warning disable RS1035
+ CultureInfo originalCulture = CultureInfo.CurrentCulture;
+ CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
+ try
+ {
+ Emitter emitter = new(sourceProductionContext);
+ emitter.EmitPocoType(pocoSpec);
+ }
+ finally
+ {
+ CultureInfo.CurrentCulture = originalCulture;
+ }
+#pragma warning restore RS1035
+ }
+
///
/// Instrumentation helper for unit tests.
///
diff --git a/src/libraries/System.Text.Json/gen/Model/PocoTypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/Model/PocoTypeGenerationSpec.cs
new file mode 100644
index 00000000000000..af178ce88ac6ff
--- /dev/null
+++ b/src/libraries/System.Text.Json/gen/Model/PocoTypeGenerationSpec.cs
@@ -0,0 +1,60 @@
+// 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;
+using SourceGenerators;
+
+namespace System.Text.Json.SourceGeneration
+{
+ ///
+ /// Represents a type annotated with the parameterless [JsonSerializable] attribute.
+ /// The source generator will produce a backing JsonSerializerContext and a static
+ /// JsonTypeInfo property on the annotated partial type.
+ ///
+ [DebuggerDisplay("Type = {TypeRef.FullyQualifiedName}")]
+ public sealed record PocoTypeGenerationSpec
+ {
+ ///
+ /// The fully qualified name and metadata of the annotated type.
+ ///
+ public required TypeRef TypeRef { get; init; }
+
+ ///
+ /// The short (unqualified) name of the annotated type.
+ ///
+ public required string TypeName { get; init; }
+
+ ///
+ /// The name to use for the generated static JsonTypeInfo property.
+ /// Defaults to "JsonTypeInfo" unless overridden by TypeInfoPropertyName.
+ ///
+ public required string TypeInfoPropertyName { get; init; }
+
+ ///
+ /// The namespace of the annotated type, or null for global namespace.
+ ///
+ public required string? Namespace { get; init; }
+
+ ///
+ /// The type declaration strings (including modifiers, keyword, name)
+ /// for the annotated type and all containing types.
+ ///
+ public required ImmutableEquatableArray TypeDeclarations { get; init; }
+
+ ///
+ /// The generated context class name (e.g., "__JsonContext_WeatherForecast").
+ ///
+ public required string GeneratedContextName { get; init; }
+
+ ///
+ /// The generation mode requested by the attribute.
+ ///
+ public required JsonSourceGenerationMode? GenerationMode { get; init; }
+
+ ///
+ /// Whether the annotated type is a value type (struct).
+ ///
+ public required bool IsValueType { get; init; }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/Resources/Strings.resx b/src/libraries/System.Text.Json/gen/Resources/Strings.resx
index 71ed2ca15e21bf..dd6b9e45ed3127 100644
--- a/src/libraries/System.Text.Json/gen/Resources/Strings.resx
+++ b/src/libraries/System.Text.Json/gen/Resources/Strings.resx
@@ -207,4 +207,16 @@
The type '{0}' has been annotated with 'JsonIgnoreAttribute' using 'JsonIgnoreCondition.Always' which is not valid on type declarations. The attribute will be ignored.
+
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+
+
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+
+
+ Type already contains a member named 'JsonTypeInfo'.
+
+
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+
diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf
index 79b8cd0d6dd8ba..cc6afdc852a497 100644
--- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf
+++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf
@@ -92,6 +92,26 @@
Typy anotované atributem JsonSerializableAttribute musí být třídy odvozené od třídy JsonSerializerContext.
+
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+
+
+
+ Type already contains a member named 'JsonTypeInfo'.
+ Type already contains a member named 'JsonTypeInfo'.
+
+
+
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+
+
+
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+
+ The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead.Člen {0} byl opatřen poznámkou jsonStringEnumConverter, což není v nativním AOT podporováno. Zvažte použití obecného objektu JsonStringEnumConverter<TEnum>.
diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf
index 52b5e98696508a..9562197eed9476 100644
--- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf
+++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf
@@ -92,6 +92,26 @@
Mit JsonSerializableAttribute kommentierte Typen müssen Klassen sein, die von JsonSerializerContext abgeleitet werden.
+
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+
+
+
+ Type already contains a member named 'JsonTypeInfo'.
+ Type already contains a member named 'JsonTypeInfo'.
+
+
+
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+
+
+
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+
+ The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead.Der Member "{0}" wurde mit "JsonStringEnumConverter" kommentiert, was in nativem AOT nicht unterstützt wird. Erwägen Sie stattdessen die Verwendung des generischen "JsonStringEnumConverter<TEnum>".
diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf
index 0c75870edb1d11..958cdb7b1e0b4e 100644
--- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf
+++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf
@@ -92,6 +92,26 @@
Los tipos anotados con JsonSerializableAttribute deben ser clases derivadas de JsonSerializerContext.
+
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+
+
+
+ Type already contains a member named 'JsonTypeInfo'.
+ Type already contains a member named 'JsonTypeInfo'.
+
+
+
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+
+
+
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+
+ The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead.El miembro '{0}' se ha anotado con 'JsonStringEnumConverter', que no se admite en AOT nativo. Considere la posibilidad de usar el elemento genérico "JsonStringEnumConverter<TEnum>" en su lugar.
diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf
index 6ece010bcfd65a..cd04554edd3494 100644
--- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf
+++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf
@@ -92,6 +92,26 @@
Les types annotés avec l'attribut JsonSerializable doivent être des classes dérivant de JsonSerializerContext.
+
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+
+
+
+ Type already contains a member named 'JsonTypeInfo'.
+ Type already contains a member named 'JsonTypeInfo'.
+
+
+
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+
+
+
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+
+ The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead.Le membre '{0}' a été annoté avec 'JsonStringEnumConverter', ce qui n’est pas pris en charge dans AOT natif. Utilisez plutôt le générique 'JsonStringEnumConverter<TEnum>'.
diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf
index a542b8017265e5..0adbc25f7c36e5 100644
--- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf
+++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf
@@ -92,6 +92,26 @@
I tipi annotati con JsonSerializableAttribute devono essere classi che derivano da JsonSerializerContext.
+
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+
+
+
+ Type already contains a member named 'JsonTypeInfo'.
+ Type already contains a member named 'JsonTypeInfo'.
+
+
+
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+
+
+
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+
+ The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead.Il membro '{0}' è stato annotato con 'JsonStringEnumConverter' che non è supportato in AOT nativo. Provare a usare il generico 'JsonStringEnumConverter<TEnum>'.
diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf
index 27f29423ff6a5f..a26b4bee1e6756 100644
--- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf
+++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf
@@ -92,6 +92,26 @@
JsonSerializableAttribute で注釈が付けられた型は、JsonSerializerContext から派生するクラスである必要があります。
+
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+
+
+
+ Type already contains a member named 'JsonTypeInfo'.
+ Type already contains a member named 'JsonTypeInfo'.
+
+
+
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+
+
+
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+
+ The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead.メンバー '{0}' には、ネイティブ AOT ではサポートされていない 'JsonStringEnumConverter' の注釈が付けられています。 代わりに汎用の 'JsonStringEnumConverter<TEnum>' を使用することを検討してください。
diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf
index 8832eb4d85a761..5be4d956865d64 100644
--- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf
+++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf
@@ -92,6 +92,26 @@
JsonSerializableAttribute 주석이 추가된 형식은 JsonSerializerContext에서 파생된 클래스여야 합니다.
+
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+
+
+
+ Type already contains a member named 'JsonTypeInfo'.
+ Type already contains a member named 'JsonTypeInfo'.
+
+
+
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+
+
+
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+
+ The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead.'{0}' 멤버에 네이티브 AOT에서 지원되지 않는 'JsonStringEnumConverter'로 주석이 달렸습니다. 대신 제네릭 'JsonStringEnumConverter<TEnum>'을 사용해 보세요.
diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf
index 9cdc8b227b3532..92c3802d30f66d 100644
--- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf
+++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf
@@ -92,6 +92,26 @@
Typy z adnotacjami JsonSerializableAttribute muszą być klasami pochodzącymi z elementu JsonSerializerContext.
+
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+
+
+
+ Type already contains a member named 'JsonTypeInfo'.
+ Type already contains a member named 'JsonTypeInfo'.
+
+
+
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+
+
+
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+
+ The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead.Element członkowski '{0}' został opatrzony adnotacją 'JsonStringEnumConverter', która nie jest obsługiwana w natywnym AOT. Zamiast tego należy rozważyć użycie ogólnego konwertera „JsonStringEnumConverter<TEnum>”.
diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf
index 3b74ad1d07dfde..51220c0fa31b34 100644
--- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf
+++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf
@@ -92,6 +92,26 @@
Tipos anotados com JsonSerializable Attribute devem ser classes derivadas de JsonSerializerContext.
+
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+
+
+
+ Type already contains a member named 'JsonTypeInfo'.
+ Type already contains a member named 'JsonTypeInfo'.
+
+
+
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+
+
+
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+
+ The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead.O membro "{0}" foi anotado com "JsonStringEnumConverter" que não tem suporte na AOT nativa. Considere usar o genérico "JsonStringEnumConverter<TEnum>".
diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf
index bd9e003fac6361..fe51831e0e6b73 100644
--- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf
+++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf
@@ -92,6 +92,26 @@
Типы, аннотированные атрибутом JsonSerializableAttribute, должны быть классами, производными от JsonSerializerContext.
+
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+
+
+
+ Type already contains a member named 'JsonTypeInfo'.
+ Type already contains a member named 'JsonTypeInfo'.
+
+
+
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+
+
+
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+
+ The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead.Элемент "{0}" содержит примечание JsonStringEnumConverter, что не поддерживается в собственном AOT. Вместо этого рассмотрите возможность использовать универсальный параметр JsonStringEnumConverter<TEnum>.
diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf
index 0757cda7d71717..62e2028b8e8ef0 100644
--- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf
+++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf
@@ -92,6 +92,26 @@
JsonSerializableAttribute ile not eklenen türler, JsonSerializerContext'ten türetilen sınıflar olmalıdır.
+
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+
+
+
+ Type already contains a member named 'JsonTypeInfo'.
+ Type already contains a member named 'JsonTypeInfo'.
+
+
+
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+
+
+
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+
+ The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead.'{0}' adlı üyeye yerel AOT’de desteklenmeyen 'JsonStringEnumConverter' parametresi eklendi. bunun yerine genel 'JsonStringEnumConverter<TEnum>' parametresini kullanmayı deneyin.
diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf
index 63e0251fec99f5..e6795fa7f9006f 100644
--- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf
+++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf
@@ -92,6 +92,26 @@
使用 JsonSerializableAttribute 批注的类型必须是派生自 JsonSerializerContext 的类。
+
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+
+
+
+ Type already contains a member named 'JsonTypeInfo'.
+ Type already contains a member named 'JsonTypeInfo'.
+
+
+
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+
+
+
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+
+ The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead.成员“{0}”已使用本机 AOT 中不支持的 "JsonStringEnumConverter" 进行批注。请改为考虑使用泛型 "JsonStringEnumConverter<TEnum>"。
diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf
index 95061df16ecbb5..9d0105c9824aa8 100644
--- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf
+++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf
@@ -92,6 +92,26 @@
以 JsonSerializableAttribute 標註的類型必須為衍生自 JsonSerializerCoNtext 的類別。
+
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+ The type '{0}' already contains a member named 'JsonTypeInfo'. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to specify a different name for the generated property.
+
+
+
+ Type already contains a member named 'JsonTypeInfo'.
+ Type already contains a member named 'JsonTypeInfo'.
+
+
+
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+ The type '{0}' has been annotated with the parameterless JsonSerializableAttribute but is not declared as partial. The type and all containing types must be made partial to kick off source generation.
+
+
+
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+ Types annotated with the parameterless JsonSerializableAttribute must be partial.
+
+ The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead.成員 '{0}' 已使用原生 AOT 不支援的 'JsonStringEnumConverter' 加上標註。請考慮改用一般 'JsonStringEnumConverter<TEnum>'。
diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets
index 0c7c4cead803df..58a2ea4b9db82b 100644
--- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets
+++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets
@@ -73,6 +73,7 @@
+
diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs
index 07091b502b1143..c4ae3b4b7f500a 100644
--- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs
+++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs
@@ -1152,10 +1152,11 @@ public sealed partial class JsonRequiredAttribute : System.Text.Json.Serializati
{
public JsonRequiredAttribute() { }
}
- [System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=true)]
+ [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Struct, AllowMultiple=true)]
public sealed partial class JsonSerializableAttribute : System.Text.Json.Serialization.JsonAttribute
{
public JsonSerializableAttribute(System.Type type) { }
+ public JsonSerializableAttribute() { }
public System.Text.Json.Serialization.JsonSourceGenerationMode GenerationMode { get { throw null; } set { } }
public string? TypeInfoPropertyName { get { throw null; } set { } }
}
From 9254de516ed312a7b4c980dc94a7585b939930ca Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 14 Apr 2026 02:00:59 +0000
Subject: [PATCH 2/3] Fix POCO source generation: use full type analysis and
emit base class declaration
- Changed ParsePocoTypeGenerationSpec to return (PocoTypeGenerationSpec, ContextGenerationSpec) tuple
- Use existing type graph traversal to build proper ContextGenerationSpec for POCO types
- Added synthetic TypeRef constructor for context types not in compilation
- Emit base class declaration in separate .Base.g.cs file for synthetic context
- All 218 unit tests pass including 7 new POCO tests
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/d115f32b-7c2c-4cca-a15d-d57ce9850f97
Co-authored-by: agocke <515774+agocke@users.noreply.github.com>
---
.../Common/src/SourceGenerators/TypeRef.cs | 12 +
.../gen/JsonSourceGenerator.Emitter.cs | 55 +++--
.../gen/JsonSourceGenerator.Parser.cs | 57 ++++-
.../gen/JsonSourceGenerator.Roslyn3.11.cs | 9 +-
.../gen/JsonSourceGenerator.Roslyn4.0.cs | 17 +-
.../JsonSourceGeneratorTests.cs | 214 ++++++++++++++++++
6 files changed, 331 insertions(+), 33 deletions(-)
diff --git a/src/libraries/Common/src/SourceGenerators/TypeRef.cs b/src/libraries/Common/src/SourceGenerators/TypeRef.cs
index a4d556ef786dbe..74997a6111a466 100644
--- a/src/libraries/Common/src/SourceGenerators/TypeRef.cs
+++ b/src/libraries/Common/src/SourceGenerators/TypeRef.cs
@@ -22,6 +22,18 @@ public TypeRef(ITypeSymbol type)
SpecialType = type.OriginalDefinition.SpecialType;
}
+ ///
+ /// Creates a TypeRef for a synthetic type that doesn't exist in the compilation.
+ ///
+ public TypeRef(string name, string fullyQualifiedName)
+ {
+ Name = name;
+ FullyQualifiedName = fullyQualifiedName;
+ IsValueType = false;
+ TypeKind = TypeKind.Class;
+ SpecialType = SpecialType.None;
+ }
+
public string Name { get; }
///
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
index fbb00130201a1e..7458c969786073 100644
--- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
@@ -146,12 +146,13 @@ public void Emit(ContextGenerationSpec contextGenerationSpec)
}
///
- /// Emits source code for a type annotated with the parameterless [JsonSerializable] attribute.
- /// Generates a file-scoped backing JsonSerializerContext and a partial type extension
- /// with a static JsonTypeInfo property.
+ /// Emits the partial type extension with a static JsonTypeInfo property
+ /// for a type annotated with the parameterless [JsonSerializable] attribute.
+ /// Also emits the base class declaration for the synthetic backing context.
///
- public void EmitPocoType(PocoTypeGenerationSpec pocoSpec)
+ public void EmitPocoTypeProperty(PocoTypeGenerationSpec pocoSpec)
{
+ // 1. Emit the partial type with the static JsonTypeInfo property
var writer = new SourceWriter();
writer.WriteLine("""
@@ -199,31 +200,47 @@ public void EmitPocoType(PocoTypeGenerationSpec pocoSpec)
{{pocoSpec.GeneratedContextName}}.Default.{{pocoSpec.TypeName}};
""");
- writer.Indentation--;
- writer.WriteLine('}');
-
- // Close containing type declarations
- for (int i = 1; i < typeDeclarations.Count; i++)
+ // Close all type declarations and namespace
+ while (writer.Indentation > 0)
{
writer.Indentation--;
writer.WriteLine('}');
}
- // Emit the file-scoped backing context class
- writer.WriteLine();
- writer.WriteLine($"""[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{s_assemblyName.Name}", "{s_assemblyName.Version}")]""");
- writer.WriteLine($"[global::System.Text.Json.Serialization.JsonSerializable(typeof({fullyQualifiedTypeName}))]");
- writer.WriteLine($"file sealed partial class {pocoSpec.GeneratedContextName} : global::System.Text.Json.Serialization.JsonSerializerContext");
- writer.WriteLine('{');
- writer.WriteLine('}');
+ AddSource($"{pocoSpec.TypeName}.JsonSerializable.g.cs", writer.ToSourceText());
+
+ // 2. Emit a separate file that provides the base class declaration for the synthetic context.
+ // The existing Emit() generates partial class files for the context but none of them
+ // declare the base class. For real user contexts, the user's source code provides this.
+ // For our synthetic context, we need this extra file.
+ var baseWriter = new SourceWriter();
+
+ baseWriter.WriteLine("""
+ //
+
+ #nullable enable annotations
+ #nullable disable warnings
+
+ """);
if (pocoSpec.Namespace != null)
{
- writer.Indentation--;
- writer.WriteLine('}');
+ baseWriter.WriteLine($"namespace {pocoSpec.Namespace}");
+ baseWriter.WriteLine('{');
+ baseWriter.Indentation++;
}
- AddSource($"{pocoSpec.TypeName}.JsonSerializable.g.cs", writer.ToSourceText());
+ baseWriter.WriteLine($"internal sealed partial class {pocoSpec.GeneratedContextName} : global::System.Text.Json.Serialization.JsonSerializerContext");
+ baseWriter.WriteLine('{');
+ baseWriter.WriteLine('}');
+
+ if (pocoSpec.Namespace != null)
+ {
+ baseWriter.Indentation--;
+ baseWriter.WriteLine('}');
+ }
+
+ AddSource($"{pocoSpec.GeneratedContextName}.Base.g.cs", baseWriter.ToSourceText());
}
private static SourceWriter CreateSourceWriterWithContextHeader(ContextGenerationSpec contextSpec, bool isPrimaryContextSourceFile = false, string? interfaceImplementation = null)
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
index c3f85d4a8c6b25..53c5156d54b479 100644
--- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
@@ -214,8 +214,10 @@ private bool HasOnlyParameterlessJsonSerializableAttributes(INamedTypeSymbol typ
///
/// Parses a type declaration annotated with the parameterless [JsonSerializable] attribute.
+ /// Returns a tuple of (PocoTypeGenerationSpec, ContextGenerationSpec) where the ContextGenerationSpec
+ /// is a synthetic context that can be emitted using the existing Emit() infrastructure.
///
- public PocoTypeGenerationSpec? ParsePocoTypeGenerationSpec(TypeDeclarationSyntax typeDeclaration, SemanticModel semanticModel, CancellationToken cancellationToken)
+ public (PocoTypeGenerationSpec Poco, ContextGenerationSpec Context)? ParsePocoTypeGenerationSpec(TypeDeclarationSyntax typeDeclaration, SemanticModel semanticModel, CancellationToken cancellationToken)
{
if (!_compilationContainsCoreJsonTypes)
{
@@ -307,18 +309,65 @@ private bool HasOnlyParameterlessJsonSerializableAttributes(INamedTypeSymbol typ
string typeName = typeSymbol.Name;
string generatedContextName = $"__JsonContext_{typeName}";
+ string? ns = typeSymbol.ContainingNamespace is { IsGlobalNamespace: false } nsSymbol ? nsSymbol.ToDisplayString() : null;
+ string fqContextName = ns != null ? $"global::{ns}.{generatedContextName}" : $"global::{generatedContextName}";
- return new PocoTypeGenerationSpec
+ // Ensure context-scoped metadata caches are empty.
+ Debug.Assert(_typesToGenerate.Count == 0);
+ Debug.Assert(_generatedTypes.Count == 0);
+
+ _contextClassLocation = location;
+
+ // Enqueue the annotated type for type graph traversal
+ _typesToGenerate.Enqueue(new TypeToGenerate
{
- TypeRef = new(typeSymbol),
+ Type = _knownSymbols.Compilation.EraseCompileTimeMetadata(typeSymbol),
+ Mode = generationMode,
+ TypeInfoPropertyName = null,
+ Location = location,
+ AttributeLocation = parameterlessAttribute.GetLocation(),
+ });
+
+ // Walk the transitive type graph generating specs for every encountered type.
+ // Use the annotated type itself as the context type for accessibility checks.
+ while (_typesToGenerate.Count > 0)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ TypeToGenerate typeToGenerate = _typesToGenerate.Dequeue();
+ if (!_generatedTypes.ContainsKey(typeToGenerate.Type))
+ {
+ TypeGenerationSpec spec = ParseTypeGenerationSpec(typeToGenerate, typeSymbol, options: null);
+ _generatedTypes.Add(typeToGenerate.Type, spec);
+ }
+ }
+
+ var contextGenSpec = new ContextGenerationSpec
+ {
+ ContextType = new TypeRef(generatedContextName, fqContextName),
+ GeneratedTypes = _generatedTypes.Values.OrderBy(t => t.TypeRef.FullyQualifiedName).ToImmutableEquatableArray(),
+ Namespace = ns,
+ ContextClassDeclarations = new[] { $"internal sealed partial class {generatedContextName}" }.ToImmutableEquatableArray(),
+ GeneratedOptionsSpec = null,
+ };
+
+ var pocoSpec = new PocoTypeGenerationSpec
+ {
+ TypeRef = new TypeRef(typeSymbol),
TypeName = typeName,
TypeInfoPropertyName = propertyName,
- Namespace = typeSymbol.ContainingNamespace is { IsGlobalNamespace: false } ns ? ns.ToDisplayString() : null,
+ Namespace = ns,
TypeDeclarations = typeDeclarations.ToImmutableEquatableArray(),
GeneratedContextName = generatedContextName,
GenerationMode = generationMode,
IsValueType = typeSymbol.IsValueType,
};
+
+ // Clear the caches of generated metadata between the processing of types.
+ _generatedTypes.Clear();
+ _typesToGenerate.Clear();
+ _contextClassLocation = null;
+
+ return (pocoSpec, contextGenSpec);
}
private static bool TryGetNestedTypeDeclarations(TypeDeclarationSyntax contextClassSyntax, SemanticModel semanticModel, CancellationToken cancellationToken, [NotNullWhen(true)] out List? typeDeclarations)
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs
index 934a94c40e8e91..e3feaeec3ae984 100644
--- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn3.11.cs
@@ -102,11 +102,14 @@ public void Execute(GeneratorExecutionContext executionContext)
foreach ((TypeDeclarationSyntax typeDeclaration, SemanticModel semanticModel) in receiver.PocoTypeDeclarations)
{
- PocoTypeGenerationSpec? pocoSpec = parser.ParsePocoTypeGenerationSpec(typeDeclaration, semanticModel, executionContext.CancellationToken);
- if (pocoSpec is not null)
+ (PocoTypeGenerationSpec Poco, ContextGenerationSpec Context)? result = parser.ParsePocoTypeGenerationSpec(typeDeclaration, semanticModel, executionContext.CancellationToken);
+ if (result is not null)
{
Emitter emitter = new(executionContext);
- emitter.EmitPocoType(pocoSpec);
+ // Emit the full backing context
+ emitter.Emit(result.Value.Context);
+ // Emit the static JsonTypeInfo property on the partial type
+ emitter.EmitPocoTypeProperty(result.Value.Poco);
}
}
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs
index 9708ade2da8333..b2b9f391542df6 100644
--- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Roslyn4.0.cs
@@ -91,7 +91,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
context.RegisterSourceOutput(diagnostics, EmitDiagnostics);
// Pipeline 2: POCO-based source generation (parameterless [JsonSerializable] on data types)
- IncrementalValuesProvider<(PocoTypeGenerationSpec?, ImmutableArray)> pocoGenerationSpecs = context.SyntaxProvider
+ IncrementalValuesProvider<((PocoTypeGenerationSpec Poco, ContextGenerationSpec Context)?, ImmutableArray)> pocoGenerationSpecs = context.SyntaxProvider
.ForAttributeWithMetadataName(
#if !ROSLYN4_4_OR_GREATER
context,
@@ -109,9 +109,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
{
#pragma warning restore RS1035
Parser parser = new(tuple.Right);
- PocoTypeGenerationSpec? pocoSpec = parser.ParsePocoTypeGenerationSpec(tuple.Left.TypeDeclaration, tuple.Left.SemanticModel, cancellationToken);
+ (PocoTypeGenerationSpec Poco, ContextGenerationSpec Context)? result = parser.ParsePocoTypeGenerationSpec(tuple.Left.TypeDeclaration, tuple.Left.SemanticModel, cancellationToken);
ImmutableArray pocoDiagnostics = parser.Diagnostics.ToImmutableArray();
- return (pocoSpec, pocoDiagnostics);
+ return (result, pocoDiagnostics);
#pragma warning disable RS1035
}
finally
@@ -121,7 +121,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
#pragma warning restore RS1035
});
- IncrementalValuesProvider pocoSpecs =
+ IncrementalValuesProvider<(PocoTypeGenerationSpec Poco, ContextGenerationSpec Context)?> pocoSpecs =
pocoGenerationSpecs.Select(static (t, _) => t.Item1);
context.RegisterSourceOutput(pocoSpecs, EmitPocoSource);
@@ -167,9 +167,9 @@ private static void EmitDiagnostics(SourceProductionContext context, ImmutableAr
}
}
- private static void EmitPocoSource(SourceProductionContext sourceProductionContext, PocoTypeGenerationSpec? pocoSpec)
+ private static void EmitPocoSource(SourceProductionContext sourceProductionContext, (PocoTypeGenerationSpec Poco, ContextGenerationSpec Context)? result)
{
- if (pocoSpec is null)
+ if (result is null)
{
return;
}
@@ -180,7 +180,10 @@ private static void EmitPocoSource(SourceProductionContext sourceProductionConte
try
{
Emitter emitter = new(sourceProductionContext);
- emitter.EmitPocoType(pocoSpec);
+ // Emit the full backing context using the existing infrastructure
+ emitter.Emit(result.Value.Context);
+ // Emit the static JsonTypeInfo property on the partial type
+ emitter.EmitPocoTypeProperty(result.Value.Poco);
}
finally
{
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs
index f28c4469971541..0368600388f642 100644
--- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs
+++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs
@@ -1239,5 +1239,219 @@ internal partial class MyContext : JsonSerializerContext { }
Compilation compilation = CompilationHelper.CreateCompilation(source);
CompilationHelper.RunJsonSourceGenerator(compilation, logger: logger);
}
+
+ [Fact]
+ public void PocoJsonSerializable_GeneratesContextAndStaticProperty()
+ {
+ string source = """
+ using System.Text.Json.Serialization;
+
+ namespace TestNamespace
+ {
+ [JsonSerializable]
+ public partial class WeatherForecast
+ {
+ public string City { get; set; }
+ public int Temperature { get; set; }
+ }
+ }
+ """;
+
+ Compilation compilation = CompilationHelper.CreateCompilation(source);
+ JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation, logger: logger);
+
+ // Verify that the generated code compiles without errors
+ result.NewCompilation.GetDiagnostics().AssertMaxSeverity(DiagnosticSeverity.Info);
+
+ // Verify no source generator diagnostics
+ Assert.Empty(result.Diagnostics);
+
+ // Verify generated source files contain the expected content
+ var generatedTrees = result.NewCompilation.SyntaxTrees
+ .Where(t => t.FilePath.Contains("WeatherForecast.JsonSerializable.g.cs"))
+ .ToArray();
+ Assert.Single(generatedTrees);
+
+ string generatedCode = generatedTrees[0].GetText().ToString();
+ Assert.Contains("partial class WeatherForecast", generatedCode);
+ Assert.Contains("JsonTypeInfo", generatedCode);
+ Assert.Contains("__JsonContext_WeatherForecast", generatedCode);
+ }
+
+ [Fact]
+ public void PocoJsonSerializable_Struct_GeneratesContextAndStaticProperty()
+ {
+ string source = """
+ using System.Text.Json.Serialization;
+
+ namespace TestNamespace
+ {
+ [JsonSerializable]
+ public partial struct Point
+ {
+ public int X { get; set; }
+ public int Y { get; set; }
+ }
+ }
+ """;
+
+ Compilation compilation = CompilationHelper.CreateCompilation(source);
+ JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation, logger: logger);
+
+ result.NewCompilation.GetDiagnostics().AssertMaxSeverity(DiagnosticSeverity.Info);
+ Assert.Empty(result.Diagnostics);
+
+ var generatedTrees = result.NewCompilation.SyntaxTrees
+ .Where(t => t.FilePath.Contains("Point.JsonSerializable.g.cs"))
+ .ToArray();
+ Assert.Single(generatedTrees);
+
+ string generatedCode = generatedTrees[0].GetText().ToString();
+ Assert.Contains("partial struct Point", generatedCode);
+ Assert.Contains("JsonTypeInfo", generatedCode);
+ }
+
+ [Fact]
+ public void PocoJsonSerializable_NonPartialType_EmitsDiagnostic()
+ {
+ string source = """
+ using System.Text.Json.Serialization;
+
+ namespace TestNamespace
+ {
+ [JsonSerializable]
+ public class NotPartialClass
+ {
+ public string Name { get; set; }
+ }
+ }
+ """;
+
+ Compilation compilation = CompilationHelper.CreateCompilation(source);
+ JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation, disableDiagnosticValidation: true);
+
+ // Expect SYSLIB1227 diagnostic
+ Assert.Contains(result.Diagnostics, d => d.Id == "SYSLIB1227");
+ }
+
+ [Fact]
+ public void PocoJsonSerializable_MemberNameCollision_EmitsDiagnostic()
+ {
+ string source = """
+ using System.Text.Json.Serialization;
+
+ namespace TestNamespace
+ {
+ [JsonSerializable]
+ public partial class MyType
+ {
+ public string Name { get; set; }
+ public static int JsonTypeInfo => 42;
+ }
+ }
+ """;
+
+ Compilation compilation = CompilationHelper.CreateCompilation(source);
+ JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation, disableDiagnosticValidation: true);
+
+ // Expect SYSLIB1228 diagnostic
+ Assert.Contains(result.Diagnostics, d => d.Id == "SYSLIB1228");
+ }
+
+ [Fact]
+ public void PocoJsonSerializable_CustomTypeInfoPropertyName_Works()
+ {
+ string source = """
+ using System.Text.Json.Serialization;
+
+ namespace TestNamespace
+ {
+ [JsonSerializable(TypeInfoPropertyName = "MyTypeInfo")]
+ public partial class MyType
+ {
+ public string Name { get; set; }
+ }
+ }
+ """;
+
+ Compilation compilation = CompilationHelper.CreateCompilation(source);
+ JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation, logger: logger);
+
+ result.NewCompilation.GetDiagnostics().AssertMaxSeverity(DiagnosticSeverity.Info);
+ Assert.Empty(result.Diagnostics);
+
+ var generatedTrees = result.NewCompilation.SyntaxTrees
+ .Where(t => t.FilePath.Contains("MyType.JsonSerializable.g.cs"))
+ .ToArray();
+ Assert.Single(generatedTrees);
+
+ string generatedCode = generatedTrees[0].GetText().ToString();
+ Assert.Contains("MyTypeInfo", generatedCode);
+ }
+
+ [Fact]
+ public void PocoJsonSerializable_CoexistsWithExplicitContext()
+ {
+ string source = """
+ using System.Text.Json.Serialization;
+
+ namespace TestNamespace
+ {
+ [JsonSerializable]
+ public partial class WeatherForecast
+ {
+ public string City { get; set; }
+ public int Temperature { get; set; }
+ }
+
+ public class OtherType
+ {
+ public string Value { get; set; }
+ }
+
+ [JsonSerializable(typeof(OtherType))]
+ internal partial class ExplicitContext : JsonSerializerContext
+ {
+ }
+ }
+ """;
+
+ Compilation compilation = CompilationHelper.CreateCompilation(source);
+ JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation, logger: logger);
+
+ result.NewCompilation.GetDiagnostics().AssertMaxSeverity(DiagnosticSeverity.Info);
+ Assert.Empty(result.Diagnostics);
+
+ // Verify both the POCO-generated and the explicit context generated code exist
+ var pocoTrees = result.NewCompilation.SyntaxTrees
+ .Where(t => t.FilePath.Contains("WeatherForecast.JsonSerializable.g.cs"))
+ .ToArray();
+ Assert.Single(pocoTrees);
+
+ var contextTrees = result.NewCompilation.SyntaxTrees
+ .Where(t => t.FilePath.Contains("ExplicitContext.g.cs"))
+ .ToArray();
+ Assert.Single(contextTrees);
+ }
+
+ [Fact]
+ public void PocoJsonSerializable_GlobalNamespace_Works()
+ {
+ string source = """
+ using System.Text.Json.Serialization;
+
+ [JsonSerializable]
+ public partial class GlobalType
+ {
+ public string Name { get; set; }
+ }
+ """;
+
+ Compilation compilation = CompilationHelper.CreateCompilation(source);
+ JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation, logger: logger);
+
+ result.NewCompilation.GetDiagnostics().AssertMaxSeverity(DiagnosticSeverity.Info);
+ Assert.Empty(result.Diagnostics);
+ }
}
}
From bf56845a89c9a1129457d7362fc5f2e260919c4d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 15 Apr 2026 03:14:42 +0000
Subject: [PATCH 3/3] Add API compat suppressions for JsonSerializableAttribute
AttributeUsage change
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/8e75d756-a303-4334-8efc-7df66c802663
Co-authored-by: agocke <515774+agocke@users.noreply.github.com>
---
.../src/CompatibilitySuppressions.xml | 21 +++++++++++++++++++
...iCompatBaseline.NetCoreAppLatestStable.xml | 6 ++++++
2 files changed, 27 insertions(+)
diff --git a/src/libraries/System.Text.Json/src/CompatibilitySuppressions.xml b/src/libraries/System.Text.Json/src/CompatibilitySuppressions.xml
index d1bee0f306888e..5db8750cb5e775 100644
--- a/src/libraries/System.Text.Json/src/CompatibilitySuppressions.xml
+++ b/src/libraries/System.Text.Json/src/CompatibilitySuppressions.xml
@@ -22,4 +22,25 @@
lib/netstandard2.0/System.Text.Json.dlltrue
+
+ CP0015
+ T:System.Text.Json.Serialization.JsonSerializableAttribute:[T:System.AttributeUsageAttribute]
+ lib/net10.0/System.Text.Json.dll
+ lib/net10.0/System.Text.Json.dll
+ true
+
+
+ CP0015
+ T:System.Text.Json.Serialization.JsonSerializableAttribute:[T:System.AttributeUsageAttribute]
+ lib/net462/System.Text.Json.dll
+ lib/net462/System.Text.Json.dll
+ true
+
+
+ CP0015
+ T:System.Text.Json.Serialization.JsonSerializableAttribute:[T:System.AttributeUsageAttribute]
+ lib/netstandard2.0/System.Text.Json.dll
+ lib/netstandard2.0/System.Text.Json.dll
+ true
+
diff --git a/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml b/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml
index 849c92f43630d9..c4d495739d0d6c 100644
--- a/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml
+++ b/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml
@@ -1027,4 +1027,10 @@
net10.0/System.Text.Json.dllnet11.0/System.Text.Json.dll
+
+ CP0015
+ T:System.Text.Json.Serialization.JsonSerializableAttribute:[T:System.AttributeUsageAttribute]
+ net10.0/System.Text.Json.dll
+ net11.0/System.Text.Json.dll
+
\ No newline at end of file