@@ -33,6 +33,7 @@ private sealed partial class Emitter
3333 private const string PropInitMethodNameSuffix = "PropInit" ;
3434 private const string TryGetTypeInfoForRuntimeCustomConverterMethodName = "TryGetTypeInfoForRuntimeCustomConverter" ;
3535 private const string ExpandConverterMethodName = "ExpandConverter" ;
36+ private const string GetConverterForNullablePropertyMethodName = "GetConverterForNullableProperty" ;
3637 private const string SerializeHandlerPropName = "SerializeHandler" ;
3738 private const string OptionsLocalVariableName = "options" ;
3839 private const string ValueVarName = "value" ;
@@ -79,6 +80,11 @@ private sealed partial class Emitter
7980 /// </summary>
8081 private readonly Dictionary < string , string > _propertyNames = new ( ) ;
8182
83+ /// <summary>
84+ /// Indicates that the type graph contains a nullable property with a design-time custom converter declaration.
85+ /// </summary>
86+ private bool _emitGetConverterForNullablePropertyMethod ;
87+
8288 /// <summary>
8389 /// The SourceText emit implementation filled by the individual Roslyn versions.
8490 /// </summary>
@@ -88,6 +94,7 @@ public void Emit(ContextGenerationSpec contextGenerationSpec)
8894 {
8995 Debug . Assert ( _typeIndex . Count == 0 ) ;
9096 Debug . Assert ( _propertyNames . Count == 0 ) ;
97+ Debug . Assert ( ! _emitGetConverterForNullablePropertyMethod ) ;
9198
9299 foreach ( TypeGenerationSpec spec in contextGenerationSpec . GeneratedTypes )
93100 {
@@ -106,14 +113,15 @@ public void Emit(ContextGenerationSpec contextGenerationSpec)
106113 string contextName = contextGenerationSpec . ContextType . Name ;
107114
108115 // Add root context implementation.
109- AddSource ( $ "{ contextName } .g.cs", GetRootJsonContextImplementation ( contextGenerationSpec ) ) ;
116+ AddSource ( $ "{ contextName } .g.cs", GetRootJsonContextImplementation ( contextGenerationSpec , _emitGetConverterForNullablePropertyMethod ) ) ;
110117
111118 // Add GetJsonTypeInfo override implementation.
112119 AddSource ( $ "{ contextName } .GetJsonTypeInfo.g.cs", GetGetTypeInfoImplementation ( contextGenerationSpec ) ) ;
113120
114121 // Add property name initialization.
115122 AddSource ( $ "{ contextName } .PropertyNames.g.cs", GetPropertyNameInitialization ( contextGenerationSpec ) ) ;
116123
124+ _emitGetConverterForNullablePropertyMethod = false ;
117125 _propertyNames . Clear ( ) ;
118126 _typeIndex . Clear ( ) ;
119127 }
@@ -539,7 +547,7 @@ private SourceText GenerateForObject(ContextGenerationSpec contextSpec, TypeGene
539547 return CompleteSourceFileAndReturnText ( writer ) ;
540548 }
541549
542- private static void GeneratePropMetadataInitFunc ( SourceWriter writer , string propInitMethodName , TypeGenerationSpec typeGenerationSpec )
550+ private void GeneratePropMetadataInitFunc ( SourceWriter writer , string propInitMethodName , TypeGenerationSpec typeGenerationSpec )
543551 {
544552 Debug . Assert ( typeGenerationSpec . PropertyGenSpecs != null ) ;
545553 ImmutableEquatableArray < PropertyGenerationSpec > properties = typeGenerationSpec . PropertyGenSpecs ;
@@ -585,9 +593,15 @@ private static void GeneratePropMetadataInitFunc(SourceWriter writer, string pro
585593 ? $ "{ JsonIgnoreConditionTypeRef } .{ property . DefaultIgnoreCondition . Value } "
586594 : "null" ;
587595
588- string converterInstantiationExpr = property . ConverterType != null
589- ? $ "({ JsonConverterTypeRef } <{ propertyTypeFQN } >){ ExpandConverterMethodName } (typeof({ propertyTypeFQN } ), new { property . ConverterType . FullyQualifiedName } (), { OptionsLocalVariableName } )"
590- : "null" ;
596+ string ? converterInstantiationExpr = null ;
597+ if ( property . ConverterType != null )
598+ {
599+ TypeRef ? nullableUnderlyingType = _typeIndex [ property . PropertyType ] . NullableUnderlyingType ;
600+ _emitGetConverterForNullablePropertyMethod |= nullableUnderlyingType != null ;
601+ converterInstantiationExpr = nullableUnderlyingType != null
602+ ? $ "{ GetConverterForNullablePropertyMethodName } <{ nullableUnderlyingType . FullyQualifiedName } >(new { property . ConverterType . FullyQualifiedName } (), { OptionsLocalVariableName } )"
603+ : $ "({ JsonConverterTypeRef } <{ propertyTypeFQN } >){ ExpandConverterMethodName } (typeof({ propertyTypeFQN } ), new { property . ConverterType . FullyQualifiedName } (), { OptionsLocalVariableName } )";
604+ }
591605
592606 writer . WriteLine ( $$ """
593607 var {{ InfoVarName }} {{ i }} = new {{ JsonPropertyInfoValuesTypeRef }} <{{ propertyTypeFQN }} >()
@@ -596,7 +610,7 @@ private static void GeneratePropMetadataInitFunc(SourceWriter writer, string pro
596610 IsPublic = {{ FormatBool ( property . IsPublic ) }} ,
597611 IsVirtual = {{ FormatBool ( property . IsVirtual ) }} ,
598612 DeclaringType = typeof({{ property . DeclaringType . FullyQualifiedName }} ),
599- Converter = {{ converterInstantiationExpr }} ,
613+ Converter = {{ converterInstantiationExpr ?? "null" }} ,
600614 Getter = {{ getterValue }} ,
601615 Setter = {{ setterValue }} ,
602616 IgnoreCondition = {{ ignoreConditionNamedArg }} ,
@@ -1007,7 +1021,7 @@ private static void GenerateTypeInfoProperty(SourceWriter writer, TypeGeneration
10071021 """ ) ;
10081022 }
10091023
1010- private static SourceText GetRootJsonContextImplementation ( ContextGenerationSpec contextSpec )
1024+ private static SourceText GetRootJsonContextImplementation ( ContextGenerationSpec contextSpec , bool emitGetConverterForNullablePropertyMethod )
10111025 {
10121026 string contextTypeRef = contextSpec . ContextType . FullyQualifiedName ;
10131027 string contextTypeName = contextSpec . ContextType . Name ;
@@ -1048,7 +1062,7 @@ private static SourceText GetRootJsonContextImplementation(ContextGenerationSpec
10481062
10491063 writer . WriteLine ( ) ;
10501064
1051- GenerateConverterHelpers ( writer ) ;
1065+ GenerateConverterHelpers ( writer , emitGetConverterForNullablePropertyMethod ) ;
10521066
10531067 return CompleteSourceFileAndReturnText ( writer ) ;
10541068 }
@@ -1082,7 +1096,7 @@ private static void GetLogicForDefaultSerializerOptionsInit(ContextGenerationSpe
10821096 """ ) ;
10831097 }
10841098
1085- private static void GenerateConverterHelpers ( SourceWriter writer )
1099+ private static void GenerateConverterHelpers ( SourceWriter writer , bool emitGetConverterForNullablePropertyMethod )
10861100 {
10871101 // The generic type parameter could capture type parameters from containing types,
10881102 // so use a name that is unlikely to be used.
@@ -1109,15 +1123,20 @@ private static void GenerateConverterHelpers(SourceWriter writer)
11091123 {{ JsonConverterTypeRef }} ? converter = options.Converters[i];
11101124 if (converter?.CanConvert(type) == true)
11111125 {
1112- return {{ ExpandConverterMethodName }} (type, converter, options);
1126+ return {{ ExpandConverterMethodName }} (type, converter, options, validateCanConvert: false );
11131127 }
11141128 }
11151129
11161130 return null;
11171131 }
11181132
1119- private static {{ JsonConverterTypeRef }} {{ ExpandConverterMethodName }} ({{ TypeTypeRef }} type, {{ JsonConverterTypeRef }} converter, {{ JsonSerializerOptionsTypeRef }} options)
1133+ private static {{ JsonConverterTypeRef }} {{ ExpandConverterMethodName }} ({{ TypeTypeRef }} type, {{ JsonConverterTypeRef }} converter, {{ JsonSerializerOptionsTypeRef }} options, bool validateCanConvert = true )
11201134 {
1135+ if (validateCanConvert && !converter.CanConvert(type))
1136+ {
1137+ throw new {{ InvalidOperationExceptionTypeRef }} (string.Format("{{ ExceptionMessages . IncompatibleConverterType }} ", converter.GetType(), type));
1138+ }
1139+
11211140 if (converter is {{ JsonConverterFactoryTypeRef }} factory)
11221141 {
11231142 converter = factory.CreateConverter(type, options);
@@ -1126,15 +1145,29 @@ private static void GenerateConverterHelpers(SourceWriter writer)
11261145 throw new {{ InvalidOperationExceptionTypeRef }} (string.Format("{{ ExceptionMessages . InvalidJsonConverterFactoryOutput }} ", factory.GetType()));
11271146 }
11281147 }
1129-
1130- if (!converter.CanConvert(type))
1131- {
1132- throw new {{ InvalidOperationExceptionTypeRef }} (string.Format("{{ ExceptionMessages . IncompatibleConverterType }} ", converter.GetType(), type));
1133- }
11341148
11351149 return converter;
11361150 }
11371151 """ ) ;
1152+
1153+ if ( emitGetConverterForNullablePropertyMethod )
1154+ {
1155+ writer . WriteLine ( $$ """
1156+
1157+ private static {{ JsonConverterTypeRef }} <{{ TypeParameter }} ?> {{ GetConverterForNullablePropertyMethodName }} <{{ TypeParameter }} >({{ JsonConverterTypeRef }} converter, {{ JsonSerializerOptionsTypeRef }} options)
1158+ where {{ TypeParameter }} : struct
1159+ {
1160+ if (converter.CanConvert(typeof({{ TypeParameter }} ?)))
1161+ {
1162+ return ({{ JsonConverterTypeRef }} <{{ TypeParameter }} ?>){{ ExpandConverterMethodName }} (typeof({{ TypeParameter }} ?), converter, options, validateCanConvert: false);
1163+ }
1164+
1165+ converter = {{ ExpandConverterMethodName }} (typeof({{ TypeParameter }} ), converter, options);
1166+ {{ JsonTypeInfoTypeRef }} <{{ TypeParameter }} > typeInfo = {{ JsonMetadataServicesTypeRef }} .{{ CreateValueInfoMethodName }} <{{ TypeParameter }} >(options, converter);
1167+ return {{ JsonMetadataServicesTypeRef }} .GetNullableConverter<{{ TypeParameter }} >(typeInfo);
1168+ }
1169+ """ ) ;
1170+ }
11381171 }
11391172
11401173 private static SourceText GetGetTypeInfoImplementation ( ContextGenerationSpec contextSpec )
0 commit comments