Support generic converters on generic types with JsonConverterAttribute#123209
Support generic converters on generic types with JsonConverterAttribute#123209
Conversation
|
@copilot there appear to be build failures related to the source generator. Please fix those. |
Fixed in e5944c5. The issue was that the code tried to assign to |
eiriktsarpalis
left a comment
There was a problem hiding this comment.
@copilot you should add a few tests that check what happens if there is a mismatch in type parameters or type constraints between the two types, or if a the generic instantiation implements a converter not matching the original type. Add tests both at runtime and source generator unit tests checking for relevant diagnostic messages.
...stem.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs
Show resolved
Hide resolved
Added tests in 92d1a10:
|
|
@stephentoub @PranavSenthilnathan before we get this merged, I would appreciate feedback on the merits of this new convention. |
There was a problem hiding this comment.
Pull request overview
This PR extends JsonConverterAttribute to accept open generic converter types when applied to generic target types with matching arity, supporting both the reflection-based resolver and the source generator (including nested generic converter scenarios).
Changes:
- Runtime: construct a closed converter type from an open generic converter type when the target type is a closed generic with matching arity.
- Source generator: resolve
JsonConverterAttributefor open generic (including nested) converter types by constructing the appropriate closed converter type. - Tests: add reflection and source-generation test coverage for supported scenarios and key failure modes (e.g., arity mismatch diagnostics).
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Converters.cs | Constructs closed generic converter types at runtime when attribute specifies an open generic converter with matching arity. |
| src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs | Adds logic to close unbound generic (including nested) converter types during generator parsing. |
| src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Attribute.cs | Adds reflection-based tests covering open generic converters, nested converters, constraints, and mismatch scenarios. |
| src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs | Adds generator diagnostic tests for arity mismatch and verifies generator behavior for other scenarios. |
| src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs | Adds compilation helpers to generate test sources for the new diagnostic and nested/constrained scenarios. |
| src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs | Adds source-gen test model types/converters for open generic and nested generic converter cases. |
| src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs | Adds integration tests validating source-gen contexts can serialize/deserialize with open generic converters. |
@eiriktsarpalis what's the downside? |
None that I'm aware of. With the exception of debugger proxies I'm not aware of any serializers/source generators applying this convention, so it might be difficult for users to discover. |
|
It seems fine to me. Hard to imagine what else it could mean. |
|
@copilot please file a PR against https://github.com/dotnet/docs extending the https://github.com/dotnet/docs/blob/main/docs/standard/serialization/system-text-json/converters-how-to.md article with instructions and examples of how to use this new convention. Explain how in certain cases it provides an AOT compatible alternative JsonConverterFactory. |
f77e83d to
3ee8f69
Compare
eiriktsarpalis
left a comment
There was a problem hiding this comment.
@stephentoub I would appreciate your review here.
|
@copilot please invoke the code-review skill and post the analysis/results as a comment on this PR |
...t.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Converters.cs
Show resolved
Hide resolved
...System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs
Outdated
Show resolved
Hide resolved
...System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs
Show resolved
Hide resolved
🤖 Copilot Code Review — PR #113609Holistic AssessmentMotivation: The PR addresses a clear gap — Approach: The reflection path is minimal and correct — a single Summary: Detailed Findings✅ Reflection path — Clean and correctThe reflection change (Converters.cs:195-199) is minimal:
No issues found here.
|
…converter tests Co-authored-by: stephentoub <[email protected]>
...stem.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs
Show resolved
Hide resolved
… levels (3+1+1) Co-authored-by: stephentoub <[email protected]>
Description
JsonConverterAttributenow supports open generic converter types on generic types when the type parameter arity matches. This works for both the reflection-based provider and source generator.Example Usage
Changes
DefaultJsonTypeInfoResolver.Converters.cs: When converter type is an open generic with matching arity to
typeToConvert, automatically construct the closed generic converter type usingMakeGenericType. The reflection APIs (Type.GetGenericArguments()andType.MakeGenericType()) handle nested generic types implicitly.JsonSourceGenerator.Parser.cs:
GetTotalTypeParameterCount()to count all type parameters including those from containing types for nested genericsConstructNestedGenericType()to properly construct closed generic types for nested generic converters (e.g.,Container<>.NestedConverter<>)typeToConvertthrough converter attribute resolution to enable open generic detectionINamedTypeSymbolonly exposesTypeParametersfor the immediate type, unlike reflection'sType.GetGenericArguments()which returns all type parameters across the hierarchyTests: Comprehensive coverage across reflection, source generator integration, and source generator unit tests including:
Option<T>withOptionConverter<T>)Result<TValue, TError>withResultConverter<TValue, TError>)Container<>.NestedConverter<>)OuterGeneric<>.MiddleNonGeneric.InnerConverter<>)NonGenericOuter.SingleLevelConverter<>)Level1<,,>.Level2<>.Level3Converter<>with 5 params split 3+1+1)where T : class)[Theory]with[InlineData]where applicable per codebase conventionsSupported Scenarios
[JsonConverter(typeof(OptionConverter<>))]onOption<T>[JsonConverter(typeof(ResultConverter<,>))]onResult<TValue, TError>[JsonConverter(typeof(Container<>.NestedConverter<>))]onTypeWithNestedConverter<T1, T2>— type parameters matched in order of definition regardless of nestedness[JsonConverter(typeof(OuterGeneric<>.MiddleNonGeneric.InnerConverter<>))]— generic within non-generic within generic[JsonConverter(typeof(Level1<,,>.Level2<>.Level3Converter<>))]— each nesting level consumes a different number of type parameters (3+1+1)[JsonConverter(typeof(NonGenericOuter.SingleLevelConverter<>))]ConverterWithClassConstraint<T> where T : classError Handling
ArgumentExceptionat runtime; source generator emits warning diagnosticArgumentExceptionat runtimeInvalidOperationExceptionat runtime (converter doesn't convert the target type)Original prompt
JsonConverterAttributeshould support generic converters on generic types #123208💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.