Skip to content

Commit

Permalink
Support JsonConverterFactory with src-gen (#58398)
Browse files Browse the repository at this point in the history
  • Loading branch information
steveharter committed Sep 3, 2021
1 parent 13d5fa0 commit 0becc99
Show file tree
Hide file tree
Showing 13 changed files with 725 additions and 234 deletions.
132 changes: 84 additions & 48 deletions src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,19 @@ public sealed partial class JsonSourceGenerator
private sealed partial class Emitter
{
// Literals in generated source
private const string RuntimeCustomConverterFetchingMethodName = "GetRuntimeProvidedCustomConverter";
private const string OptionsInstanceVariableName = "Options";
private const string PropInitMethodNameSuffix = "PropInit";
private const string CtorParamInitMethodNameSuffix = "CtorParamInit";
private const string SerializeMethodNameSuffix = "Serialize";
private const string CreateValueInfoMethodName = "CreateValueInfo";
private const string CtorParamInitMethodNameSuffix = "CtorParamInit";
private const string DefaultOptionsStaticVarName = "s_defaultOptions";
private const string DefaultContextBackingStaticVarName = "s_defaultContext";
private const string WriterVarName = "writer";
private const string ValueVarName = "value";
internal const string GetConverterFromFactoryMethodName = "GetConverterFromFactory";
private const string JsonSerializerContextName = "JsonSerializerContext";
internal const string JsonContextVarName = "jsonContext";
private const string OptionsInstanceVariableName = "Options";
private const string PropInitMethodNameSuffix = "PropInit";
private const string RuntimeCustomConverterFetchingMethodName = "GetRuntimeProvidedCustomConverter";
private const string SerializeMethodNameSuffix = "Serialize";
private const string ValueVarName = "value";
private const string WriterVarName = "writer";

private static AssemblyName _assemblyName = typeof(Emitter).Assembly.GetName();
private static readonly string s_generatedCodeAttributeSource = $@"
Expand Down Expand Up @@ -99,15 +101,24 @@ public void Emit()
{
_currentContext = contextGenerationSpec;

bool generateGetConverterMethodForTypes = false;
bool generateGetConverterMethodForProperties = false;

foreach (TypeGenerationSpec typeGenerationSpec in _currentContext.RootSerializableTypes)
{
GenerateTypeInfo(typeGenerationSpec);

generateGetConverterMethodForTypes |= typeGenerationSpec.HasTypeFactoryConverter;
generateGetConverterMethodForProperties |= typeGenerationSpec.HasPropertyFactoryConverters;
}

string contextName = _currentContext.ContextType.Name;

// Add root context implementation.
AddSource($"{contextName}.g.cs", GetRootJsonContextImplementation(), isRootContextDef: true);
AddSource(
$"{contextName}.g.cs",
GetRootJsonContextImplementation(generateGetConverterMethodForTypes, generateGetConverterMethodForProperties),
isRootContextDef: true);

// Add GetJsonTypeInfo override implementation.
AddSource($"{contextName}.GetJsonTypeInfo.g.cs", GetGetTypeInfoImplementation());
Expand Down Expand Up @@ -283,54 +294,42 @@ private string GenerateForTypeWithUnknownConverter(TypeGenerationSpec typeMetada
string typeCompilableName = typeMetadata.TypeRef;
string typeFriendlyName = typeMetadata.TypeInfoPropertyName;

string metadataInitSource;

// TODO (https://github.com/dotnet/runtime/issues/52218): consider moving this verification source to common helper.
StringBuilder metadataInitSource = new(
$@"{JsonConverterTypeRef} converter = {typeMetadata.ConverterInstantiationLogic};
{TypeTypeRef} typeToConvert = typeof({typeCompilableName});");

if (typeMetadata.IsValueType)
{
metadataInitSource = $@"{JsonConverterTypeRef} converter = {typeMetadata.ConverterInstantiationLogic};
{TypeTypeRef} typeToConvert = typeof({typeCompilableName});
if (!converter.CanConvert(typeToConvert))
{{
{TypeTypeRef}? underlyingType = {NullableTypeRef}.GetUnderlyingType(typeToConvert);
if (underlyingType != null && converter.CanConvert(underlyingType))
metadataInitSource.Append($@"
if (!converter.CanConvert(typeToConvert))
{{
{JsonConverterTypeRef}? actualConverter = converter;
if (converter is {JsonConverterFactoryTypeRef} converterFactory)
{TypeTypeRef}? underlyingType = {NullableTypeRef}.GetUnderlyingType(typeToConvert);
if (underlyingType != null && converter.CanConvert(underlyingType))
{{
actualConverter = converterFactory.CreateConverter(underlyingType, {OptionsInstanceVariableName});
if (actualConverter == null || actualConverter is {JsonConverterFactoryTypeRef})
{{
throw new {InvalidOperationExceptionTypeRef}($""JsonConverterFactory '{{converter}} cannot return a 'null' or 'JsonConverterFactory' value."");
}}
// Allow nullable handling to forward to the underlying type's converter.
converter = {JsonMetadataServicesTypeRef}.GetNullableConverter<{typeCompilableName}>(this.{typeFriendlyName})!;
converter = (({ JsonConverterFactoryTypeRef })converter).CreateConverter(typeToConvert, { OptionsInstanceVariableName })!;
}}
// Allow nullable handling to forward to the underlying type's converter.
converter = {JsonMetadataServicesTypeRef}.GetNullableConverter<{typeCompilableName}>(this.{typeFriendlyName});
}}
else
{{
throw new {InvalidOperationExceptionTypeRef}($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'."");
}}
}}
_{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, converter);";
else
{{
throw new {InvalidOperationExceptionTypeRef}($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'."");
}}
}}");
}
else
{
metadataInitSource = $@"{JsonConverterTypeRef} converter = {typeMetadata.ConverterInstantiationLogic};
{TypeTypeRef} typeToConvert = typeof({typeCompilableName});
if (!converter.CanConvert(typeToConvert))
{{
throw new {InvalidOperationExceptionTypeRef}($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'."");
}}
_{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, converter);";
metadataInitSource.Append($@"
if (!converter.CanConvert(typeToConvert))
{{
throw new {InvalidOperationExceptionTypeRef}($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'."");
}}");
}

return GenerateForType(typeMetadata, metadataInitSource);
metadataInitSource.Append($@"
_{typeFriendlyName} = { JsonMetadataServicesTypeRef }.{ GetCreateValueInfoMethodRef(typeCompilableName)} ({ OptionsInstanceVariableName}, converter); ");

return GenerateForType(typeMetadata, metadataInitSource.ToString());
}

private string GenerateForNullable(TypeGenerationSpec typeMetadata)
Expand Down Expand Up @@ -484,7 +483,7 @@ private string GenerateFastPathFuncForEnumerable(TypeGenerationSpec typeGenerati

Type elementType = valueTypeGenerationSpec.Type;
string? writerMethodToCall = GetWriterMethod(elementType);

string iterationLogic;
string valueToWrite;

Expand Down Expand Up @@ -638,7 +637,6 @@ private string GenerateForObject(TypeGenerationSpec typeMetadata)
private string GeneratePropMetadataInitFunc(TypeGenerationSpec typeGenerationSpec)
{
const string PropVarName = "properties";
const string JsonContextVarName = "jsonContext";

List<PropertyGenerationSpec> properties = typeGenerationSpec.PropertyGenSpecList!;

Expand Down Expand Up @@ -1093,7 +1091,9 @@ private string WrapWithCheckForCustomConverterIfRequired(string source, string t
}}";
}

private string GetRootJsonContextImplementation()
private string GetRootJsonContextImplementation(
bool generateGetConverterMethodForTypes,
bool generateGetConverterMethodForProperties)
{
string contextTypeRef = _currentContext.ContextTypeRef;
string contextTypeName = _currentContext.ContextType.Name;
Expand All @@ -1115,6 +1115,16 @@ private string GetRootJsonContextImplementation()
{GetFetchLogicForRuntimeSpecifiedCustomConverter()}");

if (generateGetConverterMethodForProperties)
{
sb.Append(GetFetchLogicForGetCustomConverter_PropertiesWithFactories());
}

if (generateGetConverterMethodForProperties || generateGetConverterMethodForTypes)
{
sb.Append(GetFetchLogicForGetCustomConverter_TypesWithFactories());
}

return sb.ToString();
}

Expand Down Expand Up @@ -1173,6 +1183,32 @@ private string GetFetchLogicForRuntimeSpecifiedCustomConverter()
}}";
}

private string GetFetchLogicForGetCustomConverter_PropertiesWithFactories()
{
return @$"
private {JsonConverterTypeRef}<T> {GetConverterFromFactoryMethodName}<T>({JsonConverterFactoryTypeRef} factory)
{{
return ({JsonConverterTypeRef}<T>) {GetConverterFromFactoryMethodName}(typeof(T), factory);
}}";
}

private string GetFetchLogicForGetCustomConverter_TypesWithFactories()
{
return @$"
private {JsonConverterTypeRef} {GetConverterFromFactoryMethodName}({TypeTypeRef} type, {JsonConverterFactoryTypeRef} factory)
{{
{JsonConverterTypeRef}? converter = factory.CreateConverter(type, {Emitter.OptionsInstanceVariableName});
if (converter == null || converter is {JsonConverterFactoryTypeRef})
{{
throw new {InvalidOperationExceptionTypeRef}($""The converter '{{factory.GetType()}}' cannot return null or a JsonConverterFactory instance."");
}}
return converter;
}}";
}

private string GetGetTypeInfoImplementation()
{
StringBuilder sb = new();
Expand Down
Loading

0 comments on commit 0becc99

Please sign in to comment.