Skip to content

Commit 9d7840c

Browse files
Implement JsonStringEnumConverter<TEnum> (#87224)
* Fix NativeAOT support for JsonStringEnumConverter. * Fix merge issue. * Implement JsonStringEnumConverter<TEnum>. * Remove leftover comments. * Revert unnecessary suppression. * Update compatibility suppressions. * Add missing XML docs * Fix capitalization. * add local project compatibility suppressions * apply suggestion in all location * Fix failing test. * Ensure both converter factories throw an appropriate exception when passed invalid inputs.
1 parent 8685cac commit 9d7840c

38 files changed

+617
-277
lines changed

src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs

+3
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ public KnownTypeSymbols(Compilation compilation)
197197
public INamedTypeSymbol? JsonUnmappedMemberHandlingAttributeType => GetOrResolveType("System.Text.Json.Serialization.JsonUnmappedMemberHandlingAttribute", ref _JsonUnmappedMemberHandlingAttributeType);
198198
private Option<INamedTypeSymbol?> _JsonUnmappedMemberHandlingAttributeType;
199199

200+
public INamedTypeSymbol? JsonStringEnumConverterType => GetOrResolveType("System.Text.Json.Serialization.JsonStringEnumConverter", ref _JsonStringEnumConverterType);
201+
private Option<INamedTypeSymbol?> _JsonStringEnumConverterType;
202+
200203
// Unsupported types
201204
public INamedTypeSymbol? DelegateType => _DelegateType ??= Compilation.GetSpecialType(SpecialType.System_Delegate);
202205
private INamedTypeSymbol? _DelegateType;

src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs

+8
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ internal static class DiagnosticDescriptors
7272
category: JsonConstants.SystemTextJsonSourceGenerationName,
7373
defaultSeverity: DiagnosticSeverity.Warning,
7474
isEnabledByDefault: true);
75+
76+
public static DiagnosticDescriptor JsonStringEnumConverterNotSupportedInAot { get; } = new DiagnosticDescriptor(
77+
id: "SYSLIB1040",
78+
title: new LocalizableResourceString(nameof(SR.JsonStringEnumConverterNotSupportedTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
79+
messageFormat: new LocalizableResourceString(nameof(SR.JsonStringEnumConverterNotSupportedMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
80+
category: JsonConstants.SystemTextJsonSourceGenerationName,
81+
defaultSeverity: DiagnosticSeverity.Warning,
82+
isEnabledByDefault: true);
7583
}
7684
}
7785
}

src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs

+69-42
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,13 @@ private static SourceText GenerateForTypeWithBuiltInConverter(ContextGenerationS
225225

226226
string typeFQN = typeMetadata.TypeRef.FullyQualifiedName;
227227
string typeInfoPropertyName = typeMetadata.TypeInfoPropertyName;
228-
string metadataInitSource = $"{JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.{typeInfoPropertyName}Converter);";
229-
GenerateTypeInfoProperty(writer, typeMetadata, metadataInitSource);
228+
229+
GenerateTypeInfoFactoryHeader(writer, typeMetadata);
230+
writer.WriteLine($"""
231+
{JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.{typeInfoPropertyName}Converter);
232+
""");
233+
234+
GenerateTypeInfoFactoryFooter(writer);
230235

231236
return CompleteSourceFileAndReturnText(writer);
232237
}
@@ -238,12 +243,16 @@ private static SourceText GenerateForTypeWithCustomConverter(ContextGenerationSp
238243
SourceWriter writer = CreateSourceWriterWithContextHeader(contextSpec);
239244

240245
string typeFQN = typeMetadata.TypeRef.FullyQualifiedName;
241-
string metadataInitSource = $$"""
242-
{{JsonConverterTypeRef}} converter = {{ExpandConverterMethodName}}(typeof({{typeFQN}}), new {{typeMetadata.ConverterType.FullyQualifiedName}}(), {{OptionsLocalVariableName}});
243-
{{JsonTypeInfoReturnValueLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.{{GetCreateValueInfoMethodRef(typeFQN)}} ({{OptionsLocalVariableName}}, converter);
244-
""";
246+
string converterFQN = typeMetadata.ConverterType.FullyQualifiedName;
247+
248+
GenerateTypeInfoFactoryHeader(writer, typeMetadata);
245249

246-
GenerateTypeInfoProperty(writer, typeMetadata, metadataInitSource);
250+
writer.WriteLine($"""
251+
{JsonConverterTypeRef} converter = {ExpandConverterMethodName}(typeof({typeFQN}), new {converterFQN}(), {OptionsLocalVariableName});
252+
{JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)} ({OptionsLocalVariableName}, converter);
253+
""");
254+
255+
GenerateTypeInfoFactoryFooter(writer);
247256

248257
return CompleteSourceFileAndReturnText(writer);
249258
}
@@ -257,13 +266,14 @@ private static SourceText GenerateForNullable(ContextGenerationSpec contextSpec,
257266
string typeFQN = typeMetadata.TypeRef.FullyQualifiedName;
258267
string underlyingTypeFQN = typeMetadata.NullableUnderlyingType.FullyQualifiedName;
259268

260-
string metadataInitSource = $$"""
261-
{{JsonTypeInfoReturnValueLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.{{GetCreateValueInfoMethodRef(typeFQN)}}(
262-
{{OptionsLocalVariableName}},
263-
{{JsonMetadataServicesTypeRef}}.GetNullableConverter<{{underlyingTypeFQN}}>({{OptionsLocalVariableName}}));
264-
""";
269+
GenerateTypeInfoFactoryHeader(writer, typeMetadata);
270+
271+
writer.WriteLine($$"""
272+
{{JsonConverterTypeRef}} converter = {{JsonMetadataServicesTypeRef}}.GetNullableConverter<{{underlyingTypeFQN}}>({{OptionsLocalVariableName}});
273+
{{JsonTypeInfoReturnValueLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.{{GetCreateValueInfoMethodRef(typeFQN)}}({{OptionsLocalVariableName}}, converter);
274+
""");
265275

266-
GenerateTypeInfoProperty(writer, typeMetadata, metadataInitSource);
276+
GenerateTypeInfoFactoryFooter(writer);
267277

268278
return CompleteSourceFileAndReturnText(writer);
269279
}
@@ -273,9 +283,13 @@ private static SourceText GenerateForUnsupportedType(ContextGenerationSpec conte
273283
SourceWriter writer = CreateSourceWriterWithContextHeader(contextSpec);
274284

275285
string typeFQN = typeMetadata.TypeRef.FullyQualifiedName;
276-
string metadataInitSource = $"{JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.GetUnsupportedTypeConverter<{typeFQN}>());";
277286

278-
GenerateTypeInfoProperty(writer, typeMetadata, metadataInitSource);
287+
GenerateTypeInfoFactoryHeader(writer, typeMetadata);
288+
writer.WriteLine($"""
289+
{JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.GetUnsupportedTypeConverter<{typeFQN}>());
290+
""");
291+
292+
GenerateTypeInfoFactoryFooter(writer);
279293

280294
return CompleteSourceFileAndReturnText(writer);
281295
}
@@ -285,8 +299,13 @@ private static SourceText GenerateForEnum(ContextGenerationSpec contextSpec, Typ
285299
SourceWriter writer = CreateSourceWriterWithContextHeader(contextSpec);
286300

287301
string typeFQN = typeMetadata.TypeRef.FullyQualifiedName;
288-
string metadataInitSource = $"{JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.GetEnumConverter<{typeFQN}>({OptionsLocalVariableName}));";
289-
GenerateTypeInfoProperty(writer, typeMetadata, metadataInitSource);
302+
303+
GenerateTypeInfoFactoryHeader(writer, typeMetadata);
304+
writer.WriteLine($"""
305+
{JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.GetEnumConverter<{typeFQN}>({OptionsLocalVariableName}));
306+
""");
307+
308+
GenerateTypeInfoFactoryFooter(writer);
290309

291310
return CompleteSourceFileAndReturnText(writer);
292311
}
@@ -349,18 +368,20 @@ private SourceText GenerateForCollection(ContextGenerationSpec contextSpec, Type
349368
break;
350369
}
351370

352-
string metadataInitSource = $$"""
353-
var {{InfoVarName}} = new {{JsonCollectionInfoValuesTypeRef}}<{{typeFQN}}>()
371+
GenerateTypeInfoFactoryHeader(writer, typeGenerationSpec);
372+
373+
writer.WriteLine($$"""
374+
var {{InfoVarName}} = new {{JsonCollectionInfoValuesTypeRef}}<{{typeFQN}}>
354375
{
355376
{{ObjectCreatorPropName}} = {{FormatDefaultConstructorExpr(typeGenerationSpec)}},
356377
{{NumberHandlingPropName}} = {{GetNumberHandlingAsStr(typeGenerationSpec.NumberHandling)}},
357378
{{SerializeHandlerPropName}} = {{serializeMethodName ?? "null"}}
358379
};
359380
360381
{{JsonTypeInfoReturnValueLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.{{createCollectionMethodExpr}};
361-
""";
382+
""");
362383

363-
GenerateTypeInfoProperty(writer, typeGenerationSpec, metadataInitSource);
384+
GenerateTypeInfoFactoryFooter(writer);
364385

365386
if (serializeMethodName != null)
366387
{
@@ -491,8 +512,10 @@ private SourceText GenerateForObject(ContextGenerationSpec contextSpec, TypeGene
491512
const string ObjectInfoVarName = "objectInfo";
492513
string genericArg = typeMetadata.TypeRef.FullyQualifiedName;
493514

494-
string metadataInitSource = $$"""
495-
var {{ObjectInfoVarName}} = new {{JsonObjectInfoValuesTypeRef}}<{{genericArg}}>()
515+
GenerateTypeInfoFactoryHeader(writer, typeMetadata);
516+
517+
writer.WriteLine($$"""
518+
var {{ObjectInfoVarName}} = new {{JsonObjectInfoValuesTypeRef}}<{{genericArg}}>
496519
{
497520
{{ObjectCreatorPropName}} = {{creatorInvocation}},
498521
ObjectWithParameterizedConstructorCreator = {{parameterizedCreatorInvocation}},
@@ -503,25 +526,24 @@ private SourceText GenerateForObject(ContextGenerationSpec contextSpec, TypeGene
503526
};
504527
505528
{{JsonTypeInfoReturnValueLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.CreateObjectInfo<{{typeMetadata.TypeRef.FullyQualifiedName}}>({{OptionsLocalVariableName}}, {{ObjectInfoVarName}});
506-
""";
529+
""");
507530

508-
if (typeMetadata.UnmappedMemberHandling != null)
531+
if (typeMetadata is { UnmappedMemberHandling: not null } or { PreferredPropertyObjectCreationHandling: not null })
509532
{
510-
metadataInitSource += $"""
511-
512-
{JsonTypeInfoReturnValueLocalVariableName}.{UnmappedMemberHandlingPropName} = {GetUnmappedMemberHandlingAsStr(typeMetadata.UnmappedMemberHandling.Value)};
513-
""";
514-
}
533+
writer.WriteLine();
515534

516-
if (typeMetadata.PreferredPropertyObjectCreationHandling != null)
517-
{
518-
metadataInitSource += $"""
535+
if (typeMetadata.UnmappedMemberHandling != null)
536+
{
537+
writer.WriteLine($"{JsonTypeInfoReturnValueLocalVariableName}.{UnmappedMemberHandlingPropName} = {GetUnmappedMemberHandlingAsStr(typeMetadata.UnmappedMemberHandling.Value)};");
538+
}
519539

520-
{JsonTypeInfoReturnValueLocalVariableName}.{PreferredPropertyObjectCreationHandlingPropName} = {GetObjectCreationHandlingAsStr(typeMetadata.PreferredPropertyObjectCreationHandling.Value)};
521-
""";
540+
if (typeMetadata.PreferredPropertyObjectCreationHandling != null)
541+
{
542+
writer.WriteLine($"{JsonTypeInfoReturnValueLocalVariableName}.{PreferredPropertyObjectCreationHandlingPropName} = {GetObjectCreationHandlingAsStr(typeMetadata.PreferredPropertyObjectCreationHandling.Value)};");
543+
}
522544
}
523545

524-
GenerateTypeInfoProperty(writer, typeMetadata, metadataInitSource);
546+
GenerateTypeInfoFactoryFooter(writer);
525547

526548
if (propInitMethodName != null)
527549
{
@@ -596,15 +618,17 @@ private void GeneratePropMetadataInitFunc(SourceWriter writer, string propInitMe
596618
string? converterInstantiationExpr = null;
597619
if (property.ConverterType != null)
598620
{
621+
string converterFQN = property.ConverterType.FullyQualifiedName;
599622
TypeRef? nullableUnderlyingType = _typeIndex[property.PropertyType].NullableUnderlyingType;
600623
_emitGetConverterForNullablePropertyMethod |= nullableUnderlyingType != null;
624+
601625
converterInstantiationExpr = nullableUnderlyingType != null
602-
? $"{GetConverterForNullablePropertyMethodName}<{nullableUnderlyingType.FullyQualifiedName}>(new {property.ConverterType.FullyQualifiedName}(), {OptionsLocalVariableName})"
603-
: $"({JsonConverterTypeRef}<{propertyTypeFQN}>){ExpandConverterMethodName}(typeof({propertyTypeFQN}), new {property.ConverterType.FullyQualifiedName}(), {OptionsLocalVariableName})";
626+
? $"{GetConverterForNullablePropertyMethodName}<{nullableUnderlyingType.FullyQualifiedName}>(new {converterFQN}(), {OptionsLocalVariableName})"
627+
: $"({JsonConverterTypeRef}<{propertyTypeFQN}>){ExpandConverterMethodName}(typeof({propertyTypeFQN}), new {converterFQN}(), {OptionsLocalVariableName})";
604628
}
605629

606630
writer.WriteLine($$"""
607-
var {{InfoVarName}}{{i}} = new {{JsonPropertyInfoValuesTypeRef}}<{{propertyTypeFQN}}>()
631+
var {{InfoVarName}}{{i}} = new {{JsonPropertyInfoValuesTypeRef}}<{{propertyTypeFQN}}>
608632
{
609633
IsProperty = {{FormatBool(property.IsProperty)}},
610634
IsPublic = {{FormatBool(property.IsPublic)}},
@@ -986,7 +1010,7 @@ private static DefaultCheckType GetDefaultCheckType(ContextGenerationSpec contex
9861010
};
9871011
}
9881012

989-
private static void GenerateTypeInfoProperty(SourceWriter writer, TypeGenerationSpec typeMetadata, string metadataInitSource)
1013+
private static void GenerateTypeInfoFactoryHeader(SourceWriter writer, TypeGenerationSpec typeMetadata)
9901014
{
9911015
string typeFQN = typeMetadata.TypeRef.FullyQualifiedName;
9921016
string typeInfoPropertyName = typeMetadata.TypeInfoPropertyName;
@@ -1010,15 +1034,18 @@ private static void GenerateTypeInfoProperty(SourceWriter writer, TypeGeneration
10101034
""");
10111035

10121036
writer.Indentation += 2;
1013-
writer.WriteLine(metadataInitSource);
1037+
}
1038+
1039+
private static void GenerateTypeInfoFactoryFooter(SourceWriter writer)
1040+
{
10141041
writer.Indentation -= 2;
10151042

10161043
// NB OriginatingResolver should be the last property set by the source generator.
10171044
writer.WriteLine($$"""
10181045
}
10191046
10201047
{{JsonTypeInfoReturnValueLocalVariableName}}.{{OriginatingResolverPropertyName}} = this;
1021-
return jsonTypeInfo;
1048+
return {{JsonTypeInfoReturnValueLocalVariableName}};
10221049
}
10231050
""");
10241051
}

0 commit comments

Comments
 (0)