Skip to content

Support JsonConverterFactory with src-gen - #58398

Merged
steveharter merged 4 commits into
dotnet:mainfrom
steveharter:SrcGenFactory
Sep 3, 2021
Merged

Support JsonConverterFactory with src-gen#58398
steveharter merged 4 commits into
dotnet:mainfrom
steveharter:SrcGenFactory

Conversation

@steveharter

@stevehartersteveharter commented Aug 30, 2021

Copy link
Copy Markdown
Contributor

Addresses #58267

Expected to be ported to 6.0.

Source gen example for value types (indentation issues included):
note: usage of GetConverterFromFactory()

internalpartialclassMetadataAndSerializationContext{privateglobal::System.Text.Json.Serialization.Metadata.JsonTypeInfo<global::System.Text.Json.SourceGeneration.Tests.StructWithCustomConverterFactory>?_StructWithCustomConverterFactory;publicglobal::System.Text.Json.Serialization.Metadata.JsonTypeInfo<global::System.Text.Json.SourceGeneration.Tests.StructWithCustomConverterFactory>StructWithCustomConverterFactory{get{if(_StructWithCustomConverterFactory==null){global::System.Text.Json.Serialization.JsonConverter?customConverter;if(Options.Converters.Count>0&&(customConverter=GetRuntimeProvidedCustomConverter(typeof(global::System.Text.Json.SourceGeneration.Tests.StructWithCustomConverterFactory)))!=null){_StructWithCustomConverterFactory=global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo<global::System.Text.Json.SourceGeneration.Tests.StructWithCustomConverterFactory>(Options,customConverter);}else{global::System.Text.Json.Serialization.JsonConverterconverter=this.GetConverterFromFactory(typeof(global::System.Text.Json.SourceGeneration.Tests.StructWithCustomConverterFactory),newglobal::System.Text.Json.SourceGeneration.Tests.CustomConverterFactory());global::System.TypetypeToConvert=typeof(global::System.Text.Json.SourceGeneration.Tests.StructWithCustomConverterFactory);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.StructWithCustomConverterFactory>(this.StructWithCustomConverterFactory)!;converter=((global::System.Text.Json.Serialization.JsonConverterFactory)converter).CreateConverter(typeToConvert,Options)!;}else{thrownewglobal::System.InvalidOperationException($"The converter '{converter.GetType()}' is not compatible with the type '{typeToConvert}'.");}}_StructWithCustomConverterFactory=global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo<global::System.Text.Json.SourceGeneration.Tests.StructWithCustomConverterFactory>(Options,converter);}}return_StructWithCustomConverterFactory;}}}

and for reference types:

internalpartialclassMetadataAndSerializationContext{privateglobal::System.Text.Json.Serialization.Metadata.JsonTypeInfo<global::System.Text.Json.SourceGeneration.Tests.ClassWithCustomConverterFactory>?_ClassWithCustomConverterFactory;publicglobal::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.JsonConverterconverter=this.GetConverterFromFactory(typeof(global::System.Text.Json.SourceGeneration.Tests.ClassWithCustomConverterFactory),newglobal::System.Text.Json.SourceGeneration.Tests.CustomConverterFactory());global::System.TypetypeToConvert=typeof(global::System.Text.Json.SourceGeneration.Tests.ClassWithCustomConverterFactory);if(!converter.CanConvert(typeToConvert)){thrownewglobal::System.InvalidOperationException($"The converter '{converter.GetType()}' is not compatible with the type '{typeToConvert}'.");}_ClassWithCustomConverterFactory=global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo<global::System.Text.Json.SourceGeneration.Tests.ClassWithCustomConverterFactory>(Options,converter);}}return_ClassWithCustomConverterFactory;}}}

and for properties with factories:

properties[0]=global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo<global::System.Text.Json.SourceGeneration.Tests.SampleEnum>(
...converter: jsonContext.GetConverterFromFactory<global::System.Text.Json.SourceGeneration.Tests.SampleEnum>(newglobal::System.Text.Json.Serialization.JsonStringEnumConverter()),

and the new helper methods (optionally generated)

privateglobal::System.Text.Json.Serialization.JsonConverter<T>GetConverterFromFactory<T>(global::System.Text.Json.Serialization.JsonConverterFactoryfactory){return(global::System.Text.Json.Serialization.JsonConverter<T>)GetConverterFromFactory(typeof(T),factory);}privateglobal::System.Text.Json.Serialization.JsonConverterGetConverterFromFactory(global::System.Typetype,global::System.Text.Json.Serialization.JsonConverterFactoryfactory){global::System.Text.Json.Serialization.JsonConverter?converter=factory.CreateConverter(type,Options);if(converter==null||converterisglobal::System.Text.Json.Serialization.JsonConverterFactory){thrownewglobal::System.InvalidOperationException($"The converter '{factory.GetType()}' cannot return null or a JsonConverterFactory instance.");}returnconverter;}

@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @eiriktsarpalis, @layomia
See info in area-owners.md if you want to be subscribed.

Issue Details

Addresses #58267

Expected to be ported to 6.0.

Source gen example for value types (indentation issues included):

// <auto-generated/>
#nullable enable
namespaceSystem.Text.Json.SourceGeneration.Tests{internalpartialclassMetadataContext{privateglobal::System.Text.Json.Serialization.Metadata.JsonTypeInfo<global::System.Text.Json.SourceGeneration.Tests.StructWithCustomConverter>?_StructWithCustomConverter;publicglobal::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.JsonConverterconverter=newglobal::System.Text.Json.SourceGeneration.Tests.CustomConverter_StructWithCustomConverter();global::System.TypetypeToConvert=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{thrownewglobal::System.InvalidOperationException($"The converter '{converter.GetType()}' is not compatible with the type '{typeToConvert}'.");}}elseif(converterisglobal::System.Text.Json.Serialization.JsonConverterFactoryfactory){global::System.Text.Json.Serialization.JsonConverter?actualConverter=factory.CreateConverter(typeToConvert,Options);if(actualConverter==null||actualConverterisglobal::System.Text.Json.Serialization.JsonConverterFactory){thrownewglobal::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
namespaceSystem.Text.Json.SourceGeneration.Tests{internalpartialclassMetadataContext{privateglobal::System.Text.Json.Serialization.Metadata.JsonTypeInfo<global::System.Text.Json.SourceGeneration.Tests.ClassWithCustomConverterFactory>?_ClassWithCustomConverterFactory;publicglobal::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.JsonConverterconverter=newglobal::System.Text.Json.SourceGeneration.Tests.CustomConverterFactory();global::System.TypetypeToConvert=typeof(global::System.Text.Json.SourceGeneration.Tests.ClassWithCustomConverterFactory);if(!converter.CanConvert(typeToConvert)){thrownewglobal::System.InvalidOperationException($"The converter '{converter.GetType()}' is not compatible with the type '{typeToConvert}'.");}elseif(converterisglobal::System.Text.Json.Serialization.JsonConverterFactoryfactory){global::System.Text.Json.Serialization.JsonConverter?actualConverter=factory.CreateConverter(typeToConvert,Options);if(actualConverter==null||actualConverterisglobal::System.Text.Json.Serialization.JsonConverterFactory){thrownewglobal::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;}}}}
Author:steveharter
Assignees:steveharter
Labels:

area-System.Text.Json

Milestone:6.0.0

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.

Not relevant to the scope of this PR, but here is another instance of non-localized error messages: #58292.

Copy link
Copy Markdown
ContributorAuthor

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.

@eiriktsarpalis

Copy link
Copy Markdown
Member

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.


_{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, converter);";
metadataInitSource.Append($@"
if (!converter.CanConvert(typeToConvert))

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.

Where does this branch type test for converter factories?

Copy link
Copy Markdown
ContributorAuthor

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()).

return null;
}

if (converterType.GetCompatibleBaseClass(JsonConverterFactoryFullName) != null)

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.

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?

@eiriktsarpaliseiriktsarpalisSep 3, 2021

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.

Ah, I'm assuming it's because we're embedding the generated initialization expression as a field in the property generation spec.

Copy link
Copy Markdown
ContributorAuthor

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.

Copy link
Copy Markdown
Contributor

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.

@steveharter
steveharter merged commit 0becc99 into dotnet:mainSep 3, 2021
@steveharter
steveharter deleted the SrcGenFactory branch September 3, 2021 19:48
@steveharter

Copy link
Copy Markdown
ContributorAuthor

/backport to release/6.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/6.0: https://github.com/dotnet/runtime/actions/runs/1199230405

@ghostghost locked as resolved and limited conversation to collaborators Oct 3, 2021
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@steveharter@eiriktsarpalis@layomia