Skip to content

Add simplified [JsonSerializable] source generation for POCOs#126861

Draft
Copilot wants to merge 2 commits intomainfrom
copilot/improve-json-source-generation
Draft

Add simplified [JsonSerializable] source generation for POCOs#126861
Copilot wants to merge 2 commits intomainfrom
copilot/improve-json-source-generation

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 14, 2026

Description

Today, System.Text.Json source generation requires creating a separate JsonSerializerContext-derived class and annotating it with [JsonSerializable(typeof(T))] for every type — significantly more ceremony than the reflection API. This PR allows placing [JsonSerializable] directly on a partial class or struct to auto-generate a backing context and a static JsonTypeInfo property.

Before:

[JsonSerializable(typeof(WeatherForecast))]
internal partial class MyContext : JsonSerializerContext { }

string json = JsonSerializer.Serialize(forecast, MyContext.Default.WeatherForecast);

After:

[JsonSerializable]
public partial class WeatherForecast
{
    public string City { get; set; }
    public int Temperature { get; set; }
}

string json = JsonSerializer.Serialize(forecast, WeatherForecast.JsonTypeInfo);

Attribute changes

  • Added parameterless constructor overload to JsonSerializableAttribute
  • Expanded AttributeTargets to include Struct
  • Updated ref assembly

Source generator pipeline

  • Added second incremental pipeline in JsonSourceGenerator.Roslyn4.0.cs and equivalent logic in Roslyn3.11.cs that detects parameterless [JsonSerializable] on non-context types
  • Parser produces a (PocoTypeGenerationSpec, ContextGenerationSpec) tuple using the existing type graph traversal, so the full emitter infrastructure is reused
  • Added TypeRef(string, string) constructor for synthetic context types that don't exist in the compilation

Emitter output (per annotated type)

  • __JsonContext_<TypeName>.Base.g.cs — base class declaration for the synthetic context
  • __JsonContext_<TypeName>.g.cs + per-type files — full context implementation via existing Emit() path
  • <TypeName>.JsonSerializable.g.cs — partial type extension with public static JsonTypeInfo<T> JsonTypeInfo property

Diagnostics

  • SYSLIB1227 — type must be declared partial
  • SYSLIB1228 — member name collision on JsonTypeInfo (use TypeInfoPropertyName to resolve)
  • Existing SYSLIB1224 now only fires for [JsonSerializable(typeof(T))] on non-context types, not for the parameterless form

Tests

  • 7 new unit tests covering class, struct, non-partial diagnostic, name collision diagnostic, custom TypeInfoPropertyName, coexistence with explicit context, and global namespace
  • All 218 existing unit tests pass

Not included (follow-up)

  • Roslyn interceptor to rewrite JsonSerializer.Serialize(val)JsonSerializer.Serialize(val, T.JsonTypeInfo) (plan item 4 — requires significant interceptor infrastructure)

Copilot AI and others added 2 commits April 14, 2026 01:45
…tion 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>
…s 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>
""";

Compilation compilation = CompilationHelper.CreateCompilation(source);
JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation, disableDiagnosticValidation: true);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot why are we disabling diagnostic validation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests intentionally produce Warning-level diagnostics (SYSLIB1227 for non-partial type, SYSLIB1228 for member name collision). The default validation in RunJsonSourceGenerator asserts AssertMaxSeverity(DiagnosticSeverity.Info), which would fail for these cases. This is the same pattern used by other diagnostic tests in this file (lines 211, 231, 987).

@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-text-json
See info in area-owners.md if you want to be subscribed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants