-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support JsonConverterFactory with src-gen #58398
Conversation
Tagging subscribers to this area: @eiriktsarpalis, @layomia Issue DetailsAddresses #58267 Expected to be ported to 6.0. Source gen example for value types (indentation issues included): // <auto-generated/>
#nullable enable
namespace System.Text.Json.SourceGeneration.Tests
{
internal partial class MetadataContext
{
private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo<global::System.Text.Json.SourceGeneration.Tests.StructWithCustomConverter>? _StructWithCustomConverter;
public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo<global::System.Text.Json.SourceGeneration.Tests.StructWithCustomConverter> StructWithCustomConverter
{
get
{
if (_StructWithCustomConverter == null)
{
global::System.Text.Json.Serialization.JsonConverter? customConverter;
if (Options.Converters.Count > 0 && (customConverter = GetRuntimeProvidedCustomConverter(typeof(global::System.Text.Json.SourceGeneration.Tests.StructWithCustomConverter))) != null)
{
_StructWithCustomConverter = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo<global::System.Text.Json.SourceGeneration.Tests.StructWithCustomConverter>(Options, customConverter);
}
else
{
global::System.Text.Json.Serialization.JsonConverter converter = new global::System.Text.Json.SourceGeneration.Tests.CustomConverter_StructWithCustomConverter();
global::System.Type typeToConvert = typeof(global::System.Text.Json.SourceGeneration.Tests.StructWithCustomConverter);
if (!converter.CanConvert(typeToConvert))
{
global::System.Type? underlyingType = global::System.Nullable.GetUnderlyingType(typeToConvert);
if (underlyingType != null && converter.CanConvert(underlyingType))
{
// Allow nullable handling to forward to the underlying type's converter.
converter = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.GetNullableConverter<global::System.Text.Json.SourceGeneration.Tests.StructWithCustomConverter>(this.StructWithCustomConverter)!;
}
else
{
throw new global::System.InvalidOperationException($"The converter '{converter.GetType()}' is not compatible with the type '{typeToConvert}'.");
}
}
else if (converter is global::System.Text.Json.Serialization.JsonConverterFactory factory)
{
global::System.Text.Json.Serialization.JsonConverter? actualConverter = factory.CreateConverter(typeToConvert, Options);
if (actualConverter == null || actualConverter is global::System.Text.Json.Serialization.JsonConverterFactory)
{
throw new global::System.InvalidOperationException($"JsonConverterFactory '{factory.GetType()}' cannot return a 'null' or 'JsonConverterFactory' value.");
}
converter = actualConverter;
}
_StructWithCustomConverter = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo<global::System.Text.Json.SourceGeneration.Tests.StructWithCustomConverter> (Options, converter);
}
}
return _StructWithCustomConverter;
}
}
}
} and for reference types: // <auto-generated/>
#nullable enable
namespace System.Text.Json.SourceGeneration.Tests
{
internal partial class MetadataContext
{
private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo<global::System.Text.Json.SourceGeneration.Tests.ClassWithCustomConverterFactory>? _ClassWithCustomConverterFactory;
public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo<global::System.Text.Json.SourceGeneration.Tests.ClassWithCustomConverterFactory> ClassWithCustomConverterFactory
{
get
{
if (_ClassWithCustomConverterFactory == null)
{
global::System.Text.Json.Serialization.JsonConverter? customConverter;
if (Options.Converters.Count > 0 && (customConverter = GetRuntimeProvidedCustomConverter(typeof(global::System.Text.Json.SourceGeneration.Tests.ClassWithCustomConverterFactory))) != null)
{
_ClassWithCustomConverterFactory = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo<global::System.Text.Json.SourceGeneration.Tests.ClassWithCustomConverterFactory>(Options, customConverter);
}
else
{
global::System.Text.Json.Serialization.JsonConverter converter = new global::System.Text.Json.SourceGeneration.Tests.CustomConverterFactory();
global::System.Type typeToConvert = typeof(global::System.Text.Json.SourceGeneration.Tests.ClassWithCustomConverterFactory);
if (!converter.CanConvert(typeToConvert))
{
throw new global::System.InvalidOperationException($"The converter '{converter.GetType()}' is not compatible with the type '{typeToConvert}'.");
}
else if (converter is global::System.Text.Json.Serialization.JsonConverterFactory factory)
{
global::System.Text.Json.Serialization.JsonConverter? actualConverter = factory.CreateConverter(typeToConvert, Options);
if (actualConverter == null || actualConverter is global::System.Text.Json.Serialization.JsonConverterFactory)
{
throw new global::System.InvalidOperationException($"JsonConverterFactory '{factory.GetType()}' cannot return a 'null' or 'JsonConverterFactory' value.");
}
converter = actualConverter;
}
_ClassWithCustomConverterFactory = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo<global::System.Text.Json.SourceGeneration.Tests.ClassWithCustomConverterFactory> (Options, converter);
}
}
return _ClassWithCustomConverterFactory;
}
}
}
}
|
_{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, converter);"; | ||
else | ||
{{ | ||
throw new {InvalidOperationExceptionTypeRef}($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'.""); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not relevant to the scope of this PR, but here is another instance of non-localized error messages: #58292.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is a run-time exception, not design-time, I think it should remain non-localized.
Looks good, but would it be possible to incorporate a fix for converter factories specified on the property level? See #58267 (comment) on the original issue. |
d3eaf42
to
c6582d8
Compare
|
||
_{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, converter);"; | ||
metadataInitSource.Append($@" | ||
if (!converter.CanConvert(typeToConvert)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does this branch type test for converter factories?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't... The code before that that is shared between value types and reference types will call GetConverterFromFactory()
for factories.
Also the error check for CanConvert
was pre-existing from this PR. It applies to factory and non-factory cases although it may not be necessary for the factory case, as I'm not sure we double-check this in the serializer case (i.e. I don't think we verify the result from factory.CreateConverter()).
@@ -1092,6 +1178,20 @@ private static bool PropertyAccessorCanBeReferenced(MethodInfo? accessor) | |||
return null; | |||
} | |||
|
|||
if (converterType.GetCompatibleBaseClass(JsonConverterFactoryFullName) != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I was looking for this logic in the Emitter.cs diff. I know this predates your PR, but shouldn't this method live in Emitter.cs
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I'm assuming it's because we're embedding the generated initialization expression as a field in the property generation spec.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes the parser:emitter abstraction is broken here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally it should be moved to the emitter, perhaps in a later PR.
/backport to release/6.0 |
Started backporting to release/6.0: https://github.com/dotnet/runtime/actions/runs/1199230405 |
Addresses #58267
Expected to be ported to 6.0.
Source gen example for value types (indentation issues included):
note: usage of
GetConverterFromFactory()
and for reference types:
and for properties with factories:
and the new helper methods (optionally generated)