From: Eirik Tsarpalis Date: Wed, 7 Jun 2023 23:20:34 +0000 (+0100) Subject: Implement JsonStringEnumConverter (#87224) X-Git-Tag: accepted/tizen/unified/riscv/20231226.055536~1781 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=9d7840cce5b1eb2d0aaaa81883f55688e03b05bf;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Implement JsonStringEnumConverter (#87224) * Fix NativeAOT support for JsonStringEnumConverter. * Fix merge issue. * Implement JsonStringEnumConverter. * 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. --- diff --git a/src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs b/src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs index 9d02fa2..73d5bbd 100644 --- a/src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs +++ b/src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs @@ -197,6 +197,9 @@ namespace System.Text.Json.SourceGeneration public INamedTypeSymbol? JsonUnmappedMemberHandlingAttributeType => GetOrResolveType("System.Text.Json.Serialization.JsonUnmappedMemberHandlingAttribute", ref _JsonUnmappedMemberHandlingAttributeType); private Option _JsonUnmappedMemberHandlingAttributeType; + public INamedTypeSymbol? JsonStringEnumConverterType => GetOrResolveType("System.Text.Json.Serialization.JsonStringEnumConverter", ref _JsonStringEnumConverterType); + private Option _JsonStringEnumConverterType; + // Unsupported types public INamedTypeSymbol? DelegateType => _DelegateType ??= Compilation.GetSpecialType(SpecialType.System_Delegate); private INamedTypeSymbol? _DelegateType; diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs index 17d7dd5..bd0128b 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs @@ -72,6 +72,14 @@ namespace System.Text.Json.SourceGeneration category: JsonConstants.SystemTextJsonSourceGenerationName, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true); + + public static DiagnosticDescriptor JsonStringEnumConverterNotSupportedInAot { get; } = new DiagnosticDescriptor( + id: "SYSLIB1040", + title: new LocalizableResourceString(nameof(SR.JsonStringEnumConverterNotSupportedTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + messageFormat: new LocalizableResourceString(nameof(SR.JsonStringEnumConverterNotSupportedMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); } } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index d94b13e..ef46c40 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -225,8 +225,13 @@ namespace System.Text.Json.SourceGeneration string typeFQN = typeMetadata.TypeRef.FullyQualifiedName; string typeInfoPropertyName = typeMetadata.TypeInfoPropertyName; - string metadataInitSource = $"{JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.{typeInfoPropertyName}Converter);"; - GenerateTypeInfoProperty(writer, typeMetadata, metadataInitSource); + + GenerateTypeInfoFactoryHeader(writer, typeMetadata); + writer.WriteLine($""" + {JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.{typeInfoPropertyName}Converter); + """); + + GenerateTypeInfoFactoryFooter(writer); return CompleteSourceFileAndReturnText(writer); } @@ -238,12 +243,16 @@ namespace System.Text.Json.SourceGeneration SourceWriter writer = CreateSourceWriterWithContextHeader(contextSpec); string typeFQN = typeMetadata.TypeRef.FullyQualifiedName; - string metadataInitSource = $$""" - {{JsonConverterTypeRef}} converter = {{ExpandConverterMethodName}}(typeof({{typeFQN}}), new {{typeMetadata.ConverterType.FullyQualifiedName}}(), {{OptionsLocalVariableName}}); - {{JsonTypeInfoReturnValueLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.{{GetCreateValueInfoMethodRef(typeFQN)}} ({{OptionsLocalVariableName}}, converter); - """; + string converterFQN = typeMetadata.ConverterType.FullyQualifiedName; + + GenerateTypeInfoFactoryHeader(writer, typeMetadata); - GenerateTypeInfoProperty(writer, typeMetadata, metadataInitSource); + writer.WriteLine($""" + {JsonConverterTypeRef} converter = {ExpandConverterMethodName}(typeof({typeFQN}), new {converterFQN}(), {OptionsLocalVariableName}); + {JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)} ({OptionsLocalVariableName}, converter); + """); + + GenerateTypeInfoFactoryFooter(writer); return CompleteSourceFileAndReturnText(writer); } @@ -257,13 +266,14 @@ namespace System.Text.Json.SourceGeneration string typeFQN = typeMetadata.TypeRef.FullyQualifiedName; string underlyingTypeFQN = typeMetadata.NullableUnderlyingType.FullyQualifiedName; - string metadataInitSource = $$""" - {{JsonTypeInfoReturnValueLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.{{GetCreateValueInfoMethodRef(typeFQN)}}( - {{OptionsLocalVariableName}}, - {{JsonMetadataServicesTypeRef}}.GetNullableConverter<{{underlyingTypeFQN}}>({{OptionsLocalVariableName}})); - """; + GenerateTypeInfoFactoryHeader(writer, typeMetadata); + + writer.WriteLine($$""" + {{JsonConverterTypeRef}} converter = {{JsonMetadataServicesTypeRef}}.GetNullableConverter<{{underlyingTypeFQN}}>({{OptionsLocalVariableName}}); + {{JsonTypeInfoReturnValueLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.{{GetCreateValueInfoMethodRef(typeFQN)}}({{OptionsLocalVariableName}}, converter); + """); - GenerateTypeInfoProperty(writer, typeMetadata, metadataInitSource); + GenerateTypeInfoFactoryFooter(writer); return CompleteSourceFileAndReturnText(writer); } @@ -273,9 +283,13 @@ namespace System.Text.Json.SourceGeneration SourceWriter writer = CreateSourceWriterWithContextHeader(contextSpec); string typeFQN = typeMetadata.TypeRef.FullyQualifiedName; - string metadataInitSource = $"{JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.GetUnsupportedTypeConverter<{typeFQN}>());"; - GenerateTypeInfoProperty(writer, typeMetadata, metadataInitSource); + GenerateTypeInfoFactoryHeader(writer, typeMetadata); + writer.WriteLine($""" + {JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.GetUnsupportedTypeConverter<{typeFQN}>()); + """); + + GenerateTypeInfoFactoryFooter(writer); return CompleteSourceFileAndReturnText(writer); } @@ -285,8 +299,13 @@ namespace System.Text.Json.SourceGeneration SourceWriter writer = CreateSourceWriterWithContextHeader(contextSpec); string typeFQN = typeMetadata.TypeRef.FullyQualifiedName; - string metadataInitSource = $"{JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.GetEnumConverter<{typeFQN}>({OptionsLocalVariableName}));"; - GenerateTypeInfoProperty(writer, typeMetadata, metadataInitSource); + + GenerateTypeInfoFactoryHeader(writer, typeMetadata); + writer.WriteLine($""" + {JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeFQN)}({OptionsLocalVariableName}, {JsonMetadataServicesTypeRef}.GetEnumConverter<{typeFQN}>({OptionsLocalVariableName})); + """); + + GenerateTypeInfoFactoryFooter(writer); return CompleteSourceFileAndReturnText(writer); } @@ -349,8 +368,10 @@ namespace System.Text.Json.SourceGeneration break; } - string metadataInitSource = $$""" - var {{InfoVarName}} = new {{JsonCollectionInfoValuesTypeRef}}<{{typeFQN}}>() + GenerateTypeInfoFactoryHeader(writer, typeGenerationSpec); + + writer.WriteLine($$""" + var {{InfoVarName}} = new {{JsonCollectionInfoValuesTypeRef}}<{{typeFQN}}> { {{ObjectCreatorPropName}} = {{FormatDefaultConstructorExpr(typeGenerationSpec)}}, {{NumberHandlingPropName}} = {{GetNumberHandlingAsStr(typeGenerationSpec.NumberHandling)}}, @@ -358,9 +379,9 @@ namespace System.Text.Json.SourceGeneration }; {{JsonTypeInfoReturnValueLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.{{createCollectionMethodExpr}}; - """; + """); - GenerateTypeInfoProperty(writer, typeGenerationSpec, metadataInitSource); + GenerateTypeInfoFactoryFooter(writer); if (serializeMethodName != null) { @@ -491,8 +512,10 @@ namespace System.Text.Json.SourceGeneration const string ObjectInfoVarName = "objectInfo"; string genericArg = typeMetadata.TypeRef.FullyQualifiedName; - string metadataInitSource = $$""" - var {{ObjectInfoVarName}} = new {{JsonObjectInfoValuesTypeRef}}<{{genericArg}}>() + GenerateTypeInfoFactoryHeader(writer, typeMetadata); + + writer.WriteLine($$""" + var {{ObjectInfoVarName}} = new {{JsonObjectInfoValuesTypeRef}}<{{genericArg}}> { {{ObjectCreatorPropName}} = {{creatorInvocation}}, ObjectWithParameterizedConstructorCreator = {{parameterizedCreatorInvocation}}, @@ -503,25 +526,24 @@ namespace System.Text.Json.SourceGeneration }; {{JsonTypeInfoReturnValueLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.CreateObjectInfo<{{typeMetadata.TypeRef.FullyQualifiedName}}>({{OptionsLocalVariableName}}, {{ObjectInfoVarName}}); - """; + """); - if (typeMetadata.UnmappedMemberHandling != null) + if (typeMetadata is { UnmappedMemberHandling: not null } or { PreferredPropertyObjectCreationHandling: not null }) { - metadataInitSource += $""" - - {JsonTypeInfoReturnValueLocalVariableName}.{UnmappedMemberHandlingPropName} = {GetUnmappedMemberHandlingAsStr(typeMetadata.UnmappedMemberHandling.Value)}; - """; - } + writer.WriteLine(); - if (typeMetadata.PreferredPropertyObjectCreationHandling != null) - { - metadataInitSource += $""" + if (typeMetadata.UnmappedMemberHandling != null) + { + writer.WriteLine($"{JsonTypeInfoReturnValueLocalVariableName}.{UnmappedMemberHandlingPropName} = {GetUnmappedMemberHandlingAsStr(typeMetadata.UnmappedMemberHandling.Value)};"); + } - {JsonTypeInfoReturnValueLocalVariableName}.{PreferredPropertyObjectCreationHandlingPropName} = {GetObjectCreationHandlingAsStr(typeMetadata.PreferredPropertyObjectCreationHandling.Value)}; - """; + if (typeMetadata.PreferredPropertyObjectCreationHandling != null) + { + writer.WriteLine($"{JsonTypeInfoReturnValueLocalVariableName}.{PreferredPropertyObjectCreationHandlingPropName} = {GetObjectCreationHandlingAsStr(typeMetadata.PreferredPropertyObjectCreationHandling.Value)};"); + } } - GenerateTypeInfoProperty(writer, typeMetadata, metadataInitSource); + GenerateTypeInfoFactoryFooter(writer); if (propInitMethodName != null) { @@ -596,15 +618,17 @@ namespace System.Text.Json.SourceGeneration string? converterInstantiationExpr = null; if (property.ConverterType != null) { + string converterFQN = property.ConverterType.FullyQualifiedName; TypeRef? nullableUnderlyingType = _typeIndex[property.PropertyType].NullableUnderlyingType; _emitGetConverterForNullablePropertyMethod |= nullableUnderlyingType != null; + converterInstantiationExpr = nullableUnderlyingType != null - ? $"{GetConverterForNullablePropertyMethodName}<{nullableUnderlyingType.FullyQualifiedName}>(new {property.ConverterType.FullyQualifiedName}(), {OptionsLocalVariableName})" - : $"({JsonConverterTypeRef}<{propertyTypeFQN}>){ExpandConverterMethodName}(typeof({propertyTypeFQN}), new {property.ConverterType.FullyQualifiedName}(), {OptionsLocalVariableName})"; + ? $"{GetConverterForNullablePropertyMethodName}<{nullableUnderlyingType.FullyQualifiedName}>(new {converterFQN}(), {OptionsLocalVariableName})" + : $"({JsonConverterTypeRef}<{propertyTypeFQN}>){ExpandConverterMethodName}(typeof({propertyTypeFQN}), new {converterFQN}(), {OptionsLocalVariableName})"; } writer.WriteLine($$""" - var {{InfoVarName}}{{i}} = new {{JsonPropertyInfoValuesTypeRef}}<{{propertyTypeFQN}}>() + var {{InfoVarName}}{{i}} = new {{JsonPropertyInfoValuesTypeRef}}<{{propertyTypeFQN}}> { IsProperty = {{FormatBool(property.IsProperty)}}, IsPublic = {{FormatBool(property.IsPublic)}}, @@ -986,7 +1010,7 @@ namespace System.Text.Json.SourceGeneration }; } - private static void GenerateTypeInfoProperty(SourceWriter writer, TypeGenerationSpec typeMetadata, string metadataInitSource) + private static void GenerateTypeInfoFactoryHeader(SourceWriter writer, TypeGenerationSpec typeMetadata) { string typeFQN = typeMetadata.TypeRef.FullyQualifiedName; string typeInfoPropertyName = typeMetadata.TypeInfoPropertyName; @@ -1010,7 +1034,10 @@ namespace System.Text.Json.SourceGeneration """); writer.Indentation += 2; - writer.WriteLine(metadataInitSource); + } + + private static void GenerateTypeInfoFactoryFooter(SourceWriter writer) + { writer.Indentation -= 2; // NB OriginatingResolver should be the last property set by the source generator. @@ -1018,7 +1045,7 @@ namespace System.Text.Json.SourceGeneration } {{JsonTypeInfoReturnValueLocalVariableName}}.{{OriginatingResolverPropertyName}} = this; - return jsonTypeInfo; + return {{JsonTypeInfoReturnValueLocalVariableName}}; } """); } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 38b1fc4..eba9ca2 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -549,7 +549,7 @@ namespace System.Text.Json.SourceGeneration } else if (!foundDesignTimeCustomConverter && _knownSymbols.JsonConverterAttributeType.IsAssignableFrom(attributeType)) { - converterType = GetConverterTypeFromAttribute(contextType, attributeData); + converterType = GetConverterTypeFromAttribute(contextType, type, attributeData); foundDesignTimeCustomConverter = true; } @@ -1075,15 +1075,15 @@ namespace System.Text.Json.SourceGeneration out bool isRequired, out bool canUseGetter, out bool canUseSetter, - out bool isJsonIncludeInaccessible, + out bool hasJsonIncludeButIsInaccessible, out bool setterIsInitOnly); - if (isJsonIncludeInaccessible) + if (hasJsonIncludeButIsInaccessible) { ReportDiagnostic(DiagnosticDescriptors.InaccessibleJsonIncludePropertiesNotSupported, memberInfo.GetDiagnosticLocation(), new string[] { declaringType.Name, memberInfo.Name }); } - if ((!canUseGetter && !canUseSetter && !isJsonIncludeInaccessible) || + if ((!canUseGetter && !canUseSetter && !hasJsonIncludeButIsInaccessible) || !IsSymbolAccessibleWithin(memberType, within: contextType)) { // Skip the member if either of the two conditions hold @@ -1160,7 +1160,7 @@ namespace System.Text.Json.SourceGeneration if (converterType is null && _knownSymbols.JsonConverterAttributeType.IsAssignableFrom(attributeType)) { - converterType = GetConverterTypeFromAttribute(contextType, attributeData); + converterType = GetConverterTypeFromAttribute(contextType, memberInfo, attributeData); } else if (attributeType.ContainingAssembly.Name == SystemTextJsonNamespace) { @@ -1237,7 +1237,7 @@ namespace System.Text.Json.SourceGeneration out bool isRequired, out bool canUseGetter, out bool canUseSetter, - out bool isJsonIncludeInaccessible, + out bool hasJsonIncludeButIsInaccessible, out bool isSetterInitOnly) { isPublic = false; @@ -1245,7 +1245,7 @@ namespace System.Text.Json.SourceGeneration isRequired = false; canUseGetter = false; canUseSetter = false; - isJsonIncludeInaccessible = false; + hasJsonIncludeButIsInaccessible = false; isSetterInitOnly = false; switch (memberInfo) @@ -1271,7 +1271,7 @@ namespace System.Text.Json.SourceGeneration } else { - isJsonIncludeInaccessible = hasJsonInclude; + hasJsonIncludeButIsInaccessible = hasJsonInclude; } } @@ -1290,7 +1290,7 @@ namespace System.Text.Json.SourceGeneration } else { - isJsonIncludeInaccessible = hasJsonInclude; + hasJsonIncludeButIsInaccessible = hasJsonInclude; } } else @@ -1314,7 +1314,7 @@ namespace System.Text.Json.SourceGeneration else { // Unlike properties JsonIncludeAttribute is not supported for internal fields. - isJsonIncludeInaccessible = hasJsonInclude; + hasJsonIncludeButIsInaccessible = hasJsonInclude; } } break; @@ -1324,7 +1324,7 @@ namespace System.Text.Json.SourceGeneration } } - private TypeRef? GetConverterTypeFromAttribute(INamedTypeSymbol contextType, AttributeData attributeData) + private TypeRef? GetConverterTypeFromAttribute(INamedTypeSymbol contextType, ISymbol declaringSymbol, AttributeData attributeData) { Debug.Assert(_knownSymbols.JsonConverterAttributeType.IsAssignableFrom(attributeData.AttributeClass)); var converterType = (INamedTypeSymbol?)attributeData.ConstructorArguments[0].Value; @@ -1336,6 +1336,11 @@ namespace System.Text.Json.SourceGeneration return null; } + if (_knownSymbols.JsonStringEnumConverterType.IsAssignableFrom(converterType)) + { + ReportDiagnostic(DiagnosticDescriptors.JsonStringEnumConverterNotSupportedInAot, declaringSymbol.GetDiagnosticLocation(), declaringSymbol.ToDisplayString()); + } + return new TypeRef(converterType); } diff --git a/src/libraries/System.Text.Json/gen/Resources/Strings.resx b/src/libraries/System.Text.Json/gen/Resources/Strings.resx index 4b0e51d..0af8900 100644 --- a/src/libraries/System.Text.Json/gen/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/gen/Resources/Strings.resx @@ -171,4 +171,10 @@ Type '{0}' is annotated with 'JsonDerivedTypeAttribute' which is not supported in 'JsonSourceGenerationMode.Serialization'. + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf index 80c44da..f1fb86b 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf @@ -62,6 +62,16 @@ Deserializace vlastností pouze pro inicializaci se v současnosti v režimu generování zdroje nepodporuje. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + The non-generic 'JsonStringEnumConverter' requires dynamic code. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. Typ {0} má více konstruktorů anotovaných s JsonConstructorAttribute. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf index 14848a3..e50a30a 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf @@ -62,6 +62,16 @@ Die Deserialisierung von reinen init-Eigenschaften wird im Quellgenerierungsmodus derzeit nicht unterstützt. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + The non-generic 'JsonStringEnumConverter' requires dynamic code. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. Typ "{0}" weist mehrere Konstruktoren mit dem Kommentar "JsonConstructorAttribute" auf. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf index 8a97e74..d3c4116 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf @@ -62,6 +62,16 @@ Actualmente no se admite la deserialización de propiedades de solo inicialización en el modo de generación de origen. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + The non-generic 'JsonStringEnumConverter' requires dynamic code. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. El tipo '{0}' tiene varios constructores anotados con 'JsonConstructorAttribute'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf index fb3db75..304d94c 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf @@ -62,6 +62,16 @@ La désérialisation des propriétés d’initialisation uniquement n’est actuellement pas prise en charge en mode de génération de source. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + The non-generic 'JsonStringEnumConverter' requires dynamic code. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. Le type' {0} 'a plusieurs constructeurs annotés avec’JsonConstructorAttribute'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf index 596de18..d3aca5c 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf @@ -62,6 +62,16 @@ La deserializzazione delle proprietà di sola inizializzazione al momento non è supportata nella modalità di generazione di origine. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + The non-generic 'JsonStringEnumConverter' requires dynamic code. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. Il tipo '{0}' contiene più costruttori che presentano l'annotazione 'JsonConstructorAttribute'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf index 8988ba0..fe079b2 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf @@ -62,6 +62,16 @@ 現在、ソース生成モードでは init-only プロパティの逆シリアル化はサポートされていません。 + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + The non-generic 'JsonStringEnumConverter' requires dynamic code. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. 型 '{0}' には、'JsonConstructorAttribute' で注釈が付けられた複数のコンストラクターがあります。 diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf index 193d95b..81b0590 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf @@ -62,6 +62,16 @@ 초기화 전용 속성의 역직렬화는 현재 원본 생성 모드에서 지원되지 않습니다. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + The non-generic 'JsonStringEnumConverter' requires dynamic code. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. '{0}' 형식에 'JsonConstructorAttribute'로 주석이 추가된 여러 생성자가 있습니다. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf index e769635..23db1ec 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf @@ -62,6 +62,16 @@ Deserializacja właściwości tylko do inicjowania nie jest obecnie obsługiwana w trybie generowania źródła. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + The non-generic 'JsonStringEnumConverter' requires dynamic code. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. Typ "{0}" ma wiele konstruktorów z adnotacją "JsonConstructorAttribute". diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf index 5ec4bae..8339db7 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf @@ -62,6 +62,16 @@ A desserialização de propriedades apenas de inicialização não é atualmente suportada no modo de geração de origem. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + The non-generic 'JsonStringEnumConverter' requires dynamic code. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. O tipo '{0}' tem vários construtores anotados com 'JsonConstructorAttribute'. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf index 5de2a80..e5ef15b 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf @@ -62,6 +62,16 @@ Десериализация свойств, предназначенных только для инициализации, сейчас не поддерживается в режиме создания исходного кода. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + The non-generic 'JsonStringEnumConverter' requires dynamic code. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. Тип "{0}" имеет несколько конструкторов, аннотированных с использованием JsonConstructorAttribute. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf index f65e768..8076c29 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf @@ -62,6 +62,16 @@ Yalnızca başlangıç özelliklerini seri durumdan çıkarma şu anda kaynak oluşturma modunda desteklenmiyor. + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + The non-generic 'JsonStringEnumConverter' requires dynamic code. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. '{0}' türünün 'JsonConstructorAttribute' ile açıklanan birden çok oluşturucusu var. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf index 6f6087b..4b238b2 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf @@ -62,6 +62,16 @@ 源生成模式当前不支持仅初始化属性的反序列化。 + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + The non-generic 'JsonStringEnumConverter' requires dynamic code. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. 类型“{0}”具有用 “JsonConstructorAttribute” 批注的多个构造函数。 diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf index 45bce60..fa04417 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf @@ -62,6 +62,16 @@ 來源產生模式目前不支援 init-only 屬性的還原序列化。 + + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + The member '{0}' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter<TEnum>' instead. + + + + The non-generic 'JsonStringEnumConverter' requires dynamic code. + The non-generic 'JsonStringEnumConverter' requires dynamic code. + + Type '{0}' has multiple constructors annotated with 'JsonConstructorAttribute'. 類型 '{0}' 包含多個以 'JsonConstructorAttribute' 註解的建構函式。 diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index e25bbc8..7974eb6 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -1040,7 +1040,7 @@ namespace System.Text.Json.Serialization public System.Text.Json.Serialization.JsonKnownNamingPolicy PropertyNamingPolicy { get { throw null; } set { } } public bool WriteIndented { get { throw null; } set { } } } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JsonStringEnumConverter cannot be statically analyzed and requires runtime code generation. Consider authoring a custom converter that is not a factory to work around the issue. See https://github.com/dotnet/runtime/issues/73124.")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JsonStringEnumConverter cannot be statically analyzed and requires runtime code generation. Applications should use the generic JsonStringEnumConverter instead.")] public partial class JsonStringEnumConverter : System.Text.Json.Serialization.JsonConverterFactory { public JsonStringEnumConverter() { } @@ -1048,6 +1048,13 @@ namespace System.Text.Json.Serialization public sealed override bool CanConvert(System.Type typeToConvert) { throw null; } public sealed override System.Text.Json.Serialization.JsonConverter CreateConverter(System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { throw null; } } + public partial class JsonStringEnumConverter : System.Text.Json.Serialization.JsonConverterFactory where TEnum : struct, System.Enum + { + public JsonStringEnumConverter() { } + public JsonStringEnumConverter(System.Text.Json.JsonNamingPolicy? namingPolicy = null, bool allowIntegerValues = true) { } + public sealed override bool CanConvert(System.Type typeToConvert) { throw null; } + public sealed override System.Text.Json.Serialization.JsonConverter CreateConverter(System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { throw null; } + } public enum JsonUnknownDerivedTypeHandling { FailSerialization = 0, @@ -1183,7 +1190,7 @@ namespace System.Text.Json.Serialization.Metadata public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateStackInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues collectionInfo, System.Action addFunc) where TCollection : System.Collections.IEnumerable { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateStackInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues collectionInfo) where TCollection : System.Collections.Generic.Stack { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateValueInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.JsonConverter converter) { throw null; } - public static System.Text.Json.Serialization.JsonConverter GetEnumConverter(System.Text.Json.JsonSerializerOptions options) where T : struct { throw null; } + public static System.Text.Json.Serialization.JsonConverter GetEnumConverter(System.Text.Json.JsonSerializerOptions options) where T : struct, System.Enum { throw null; } public static System.Text.Json.Serialization.JsonConverter GetNullableConverter(System.Text.Json.JsonSerializerOptions options) where T : struct { throw null; } public static System.Text.Json.Serialization.JsonConverter GetNullableConverter(System.Text.Json.Serialization.Metadata.JsonTypeInfo underlyingTypeInfo) where T : struct { throw null; } public static System.Text.Json.Serialization.JsonConverter GetUnsupportedTypeConverter() { throw null; } diff --git a/src/libraries/System.Text.Json/src/CompatibilitySuppressions.xml b/src/libraries/System.Text.Json/src/CompatibilitySuppressions.xml new file mode 100644 index 0000000..1de43a6 --- /dev/null +++ b/src/libraries/System.Text.Json/src/CompatibilitySuppressions.xml @@ -0,0 +1,11 @@ + + + + + CP0015 + T:System.Text.Json.Serialization.JsonStringEnumConverter:[T:System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute] + lib/net7.0/System.Text.Json.dll + lib/net7.0/System.Text.Json.dll + true + + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index 2793687..064094d 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -549,6 +549,9 @@ The converter '{0}' cannot return an instance of JsonConverterFactory. + + The type '{0}' is not supported by the current JsonConverterFactory. + The element must be of type '{0}' diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverterFactory.cs index b23623f..ed49845 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverterFactory.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverterFactory.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace System.Text.Json.Serialization.Converters @@ -18,7 +19,10 @@ namespace System.Text.Json.Serialization.Converters } public override JsonConverter CreateConverter(Type type, JsonSerializerOptions options) - => Create(type, EnumConverterOptions.AllowNumbers, namingPolicy: null, options); + { + Debug.Assert(CanConvert(type)); + return Create(type, EnumConverterOptions.AllowNumbers, namingPolicy: null, options); + } internal static JsonConverter Create(Type enumType, EnumConverterOptions converterOptions, JsonNamingPolicy? namingPolicy, JsonSerializerOptions options) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverterFactory.cs index a674f80..53aa155 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverterFactory.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverterFactory.cs @@ -18,12 +18,10 @@ namespace System.Text.Json.Serialization.Converters public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) { - Debug.Assert(typeToConvert.GetGenericArguments().Length > 0); + Debug.Assert(typeToConvert.IsNullableOfT()); Type valueTypeToConvert = typeToConvert.GetGenericArguments()[0]; - JsonConverter valueConverter = options.GetConverterInternal(valueTypeToConvert); - Debug.Assert(valueConverter != null); // If the value type has an interface or object converter, just return that converter directly. if (!valueConverter.TypeToConvert.IsValueType && valueTypeToConvert.IsValueType) @@ -36,6 +34,7 @@ namespace System.Text.Json.Serialization.Converters public static JsonConverter CreateValueConverter(Type valueTypeToConvert, JsonConverter valueConverter) { + Debug.Assert(valueTypeToConvert.IsValueType && !valueTypeToConvert.IsNullableOfT()); return (JsonConverter)Activator.CreateInstance( GetNullableConverterType(valueTypeToConvert), BindingFlags.Instance | BindingFlags.Public, diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverterFactory.cs index bd88a2b..698e2c3 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverterFactory.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverterFactory.cs @@ -44,7 +44,10 @@ namespace System.Text.Json.Serialization.Converters } public override JsonConverter CreateConverter(Type type, JsonSerializerOptions options) - => CreateUnsupportedConverterForType(type); + { + Debug.Assert(CanConvert(type)); + return CreateUnsupportedConverterForType(type); + } internal static JsonConverter CreateUnsupportedConverterForType(Type type, string? errorMessage = null) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonStringEnumConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonStringEnumConverter.cs index 8dfe234..4210d66 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonStringEnumConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonStringEnumConverter.cs @@ -12,10 +12,64 @@ namespace System.Text.Json.Serialization /// /// Reading is case insensitive, writing can be customized via a . /// + /// The enum type that this converter targets. + public class JsonStringEnumConverter : JsonConverterFactory + where TEnum : struct, Enum + { + private readonly JsonNamingPolicy? _namingPolicy; + private readonly EnumConverterOptions _converterOptions; + + /// + /// Constructor. Creates the with the + /// default naming policy and allows integer values. + /// + public JsonStringEnumConverter() : this(namingPolicy: null, allowIntegerValues: true) + { + // An empty constructor is needed for construction via attributes + } + + /// + /// Constructor. + /// + /// + /// Optional naming policy for writing enum values. + /// + /// + /// True to allow undefined enum values. When true, if an enum value isn't + /// defined it will output as a number rather than a string. + /// + public JsonStringEnumConverter(JsonNamingPolicy? namingPolicy = null, bool allowIntegerValues = true) + { + _namingPolicy = namingPolicy; + _converterOptions = allowIntegerValues + ? EnumConverterOptions.AllowNumbers | EnumConverterOptions.AllowStrings + : EnumConverterOptions.AllowStrings; + } + + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(TEnum); + + /// + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + if (typeToConvert != typeof(TEnum)) + { + ThrowHelper.ThrowArgumentOutOfRangeException_JsonConverterFactory_TypeNotSupported(typeToConvert); + } + + return new EnumConverter(_converterOptions, _namingPolicy, options); + } + } + + /// + /// Converter to convert enums to and from strings. + /// + /// + /// Reading is case insensitive, writing can be customized via a . + /// [RequiresDynamicCode( "JsonStringEnumConverter cannot be statically analyzed and requires runtime code generation. " + - "Consider authoring a custom converter that is not a factory to work around the issue. " + - "See https://github.com/dotnet/runtime/issues/73124.")] + "Applications should use the generic JsonStringEnumConverter instead.")] public class JsonStringEnumConverter : JsonConverterFactory { private readonly JsonNamingPolicy? _namingPolicy; @@ -25,8 +79,7 @@ namespace System.Text.Json.Serialization /// Constructor. Creates the with the /// default naming policy and allows integer values. /// - public JsonStringEnumConverter() - : this(namingPolicy: null, allowIntegerValues: true) + public JsonStringEnumConverter() : this(namingPolicy: null, allowIntegerValues: true) { // An empty constructor is needed for construction via attributes } @@ -56,7 +109,14 @@ namespace System.Text.Json.Serialization } /// - public sealed override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => - EnumConverterFactory.Create(typeToConvert, _converterOptions, _namingPolicy, options); + public sealed override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + if (!typeToConvert.IsEnum) + { + ThrowHelper.ThrowArgumentOutOfRangeException_JsonConverterFactory_TypeNotSupported(typeToConvert); + } + + return EnumConverterFactory.Create(typeToConvert, _converterOptions, _namingPolicy, options); + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs index 417ed4c..b2e96d7 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs @@ -36,6 +36,12 @@ namespace System.Text.Json } [DoesNotReturn] + public static void ThrowArgumentOutOfRangeException_JsonConverterFactory_TypeNotSupported(Type typeToConvert) + { + throw new ArgumentOutOfRangeException(nameof(typeToConvert), SR.Format(SR.SerializerConverterFactoryInvalidArgument, typeToConvert.FullName)); + } + + [DoesNotReturn] public static void ThrowArgumentException_ArrayTooSmall(string paramName) { throw new ArgumentException(SR.ArrayTooSmall, paramName); diff --git a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs index 8e84113..b06f0ad 100644 --- a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs +++ b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs @@ -1544,7 +1544,6 @@ namespace System.Text.Json.Serialization.Tests } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/79311", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public async Task TestTypeWithEnumParameters() { // Regression test for https://github.com/dotnet/runtime/issues/68647 @@ -1576,7 +1575,7 @@ namespace System.Text.Json.Serialization.Tests } } - [JsonConverter(typeof(JsonStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum MyEnum { One = 1, diff --git a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs index 72dccfa..4c0b796 100644 --- a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.cs @@ -3283,7 +3283,7 @@ namespace System.Text.Json.Serialization.Tests => new DictionaryWithPrivateKeyAndValueType { _values = { [PrivateEnum.A] = PrivateEnum.B, [PrivateEnum.B] = PrivateEnum.C } }; public void Validate() => Assert.Equal(new KeyValuePair[] { new(PrivateEnum.A, PrivateEnum.B), new(PrivateEnum.B, PrivateEnum.C) }, this); - public string GetExpectedJson() => """{"A":1,"B":2}"""; // cf. https://github.com/dotnet/runtime/issues/87129 + public string GetExpectedJson() => """{"A":1,"B":2}"""; PrivateEnum IDictionary.this[PrivateEnum key] { get => _values[key]; set => _values[key] = value; } ICollection IDictionary.Keys => _values.Keys; diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs index 460ba8d..9047bf0 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs @@ -705,20 +705,19 @@ namespace System.Text.Json.SourceGeneration.Tests { } - // Regression test for https://github.com/dotnet/runtime/issues/61860 [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/79311", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public static void SupportsGenericParameterWithCustomConverterFactory() { + // Regression test for https://github.com/dotnet/runtime/issues/61860 var value = new List { TestEnum.Cee }; string json = JsonSerializer.Serialize(value, GenericParameterWithCustomConverterFactoryContext.Default.ListTestEnum); Assert.Equal(@"[""Cee""]", json); } - // Regression test for https://github.com/dotnet/runtime/issues/74652 [Fact] public static void ClassWithStringValuesRoundtrips() { + // Regression test for https://github.com/dotnet/runtime/issues/74652 JsonSerializerOptions options = ClassWithStringValuesContext.Default.Options; ClassWithStringValues obj = new() @@ -730,10 +729,10 @@ namespace System.Text.Json.SourceGeneration.Tests Assert.Equal("""{"StringValuesProperty":["abc","def"]}""", json); } - // Regression test for https://github.com/dotnet/runtime/issues/61734 [Fact] public static void ClassWithDictionaryPropertyRoundtrips() { + // Regression test for https://github.com/dotnet/runtime/issues/61734 JsonSerializerOptions options = ClassWithDictionaryPropertyContext.Default.Options; ClassWithDictionaryProperty obj = new(new Dictionary() @@ -746,7 +745,7 @@ namespace System.Text.Json.SourceGeneration.Tests Assert.Equal("""{"DictionaryProperty":{"foo":"bar","test":"baz"}}""", json); } - [JsonConverter(typeof(JsonStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum TestEnum { Aye, Bee, Cee diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs index 4fcb6c1..5100371 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs @@ -160,7 +160,7 @@ namespace System.Text.Json.SourceGeneration.Tests public bool IsIncludeFieldsEnabled => GetType().GetCustomAttribute()?.IncludeFields ?? false; } - [JsonConverter(typeof(JsonStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum EnumWrittenAsString { A = 1 @@ -233,7 +233,6 @@ namespace System.Text.Json.SourceGeneration.Tests } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/79311", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void EnsureHelperMethodGenerated_TypeFactory() { // There are 2 helper methods generated for obtaining a converter from a factory: @@ -254,7 +253,6 @@ namespace System.Text.Json.SourceGeneration.Tests } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/79311", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void EnsureHelperMethodGenerated_ImplicitPropertyFactory() { // ContextWithImplicitStringEnum does not have an entry for EnumWrittenAsString since it is diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets index 6c5d287..704790d 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets @@ -8,7 +8,8 @@ - $(NoWarn);SYSLIB0020;SYSLIB0049;SYSLIB1037;SYSLIB1038;SYSLIB1039 + + $(NoWarn);SYSLIB0020;SYSLIB0049;SYSLIB1037;SYSLIB1038;SYSLIB1039;SYSLIB1040 true diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs index 7ed8c5e..0a4ac1a 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs @@ -247,19 +247,19 @@ namespace System.Text.Json.SourceGeneration.Tests public class ClassWithCustomConverterFactoryProperty { - [JsonConverter(typeof(JsonStringEnumConverter))] // This converter is a JsonConverterFactory + [JsonConverter(typeof(JsonStringEnumConverter))] // This converter is a JsonConverterFactory public SourceGenSampleEnum MyEnum { get; set; } } public struct StructWithCustomConverterFactoryProperty { - [JsonConverter(typeof(JsonStringEnumConverter))] // This converter is a JsonConverterFactory + [JsonConverter(typeof(JsonStringEnumConverter))] // This converter is a JsonConverterFactory public SourceGenSampleEnum MyEnum { get; set; } } public class ClassWithCustomConverterFactoryNullableProperty { - [JsonConverter(typeof(JsonStringEnumConverter))] // This converter is a JsonConverterFactory + [JsonConverter(typeof(JsonStringEnumConverter))] // This converter is a JsonConverterFactory public SourceGenSampleEnum? MyEnum { get; set; } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs index 922311a..dca6239 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs @@ -617,40 +617,60 @@ namespace System.Text.Json.SourceGeneration.UnitTests return CreateCompilation(source); } - internal static void CheckDiagnosticMessages( - DiagnosticSeverity level, - ImmutableArray diagnostics, - (Location Location, string Message)[] expectedDiags, - bool sort = true) + public static Compilation CreateTypesAnnotatedWithJsonStringEnumConverter() { - ((string FileName, TextSpan, LinePositionSpan), string)[] actualDiags = diagnostics - .Where(diagnostic => diagnostic.Severity == level) - .Select(diagnostic => (GetLocationNormalForm(diagnostic.Location), diagnostic.GetMessage())) - .ToArray(); - - if (CultureInfo.CurrentUICulture.Name.StartsWith("en", StringComparison.OrdinalIgnoreCase)) - { - ((string FileName, TextSpan, LinePositionSpan), string Message)[] expectedDiagsNormalized = expectedDiags - .Select(diag => (GetLocationNormalForm(diag.Location), diag.Message)) - .ToArray(); + string source = """ + using System.Text.Json.Serialization; - if (sort) + namespace HelloWorld { - // Can't depend on reflection order when generating type metadata. - Array.Sort(actualDiags); - Array.Sort(expectedDiagsNormalized); + [JsonSerializable(typeof(MyClass))] + internal partial class JsonContext : JsonSerializerContext + { + } + + public class MyClass + { + public Enum1 Enum1Prop { get; set; } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public Enum2 Enum2Prop { get; set; } + } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum Enum1 { A, B, C }; + + public enum Enum2 { A, B, C }; } + """; - Assert.Equal(expectedDiagsNormalized, actualDiags); - } - else - { - // for non-English runs, just compare the number of messages are the same - Assert.Equal(expectedDiags.Length, actualDiags.Length); - } + return CreateCompilation(source); + } - static (string FileName, TextSpan, LinePositionSpan) GetLocationNormalForm(Location location) - => (location.SourceTree?.FilePath ?? "", location.SourceSpan, location.GetLineSpan().Span); + internal static void AssertEqualDiagnosticMessages( + IEnumerable expectedDiags, + IEnumerable actualDiags) + { + HashSet expectedSet = new(expectedDiags); + HashSet actualSet = new(actualDiags.Select(d => new DiagnosticData(d.Severity, d.Location, d.GetMessage()))); + AssertExtensions.Equal(expectedSet, actualSet); } } + + public record struct DiagnosticData( + DiagnosticSeverity Severity, + string FilePath, + LinePositionSpan LinePositionSpan, + string Message) + { + public DiagnosticData(DiagnosticSeverity severity, Location location, string message) + : this(severity, location.SourceTree?.FilePath ?? "", location.GetLineSpan().Span, TrimCultureSensitiveMessage(message)) + { + } + + // for non-English runs, trim the message content since it might be translated. + private static string TrimCultureSensitiveMessage(string message) => s_IsEnglishCulture ? message : ""; + private readonly static bool s_IsEnglishCulture = CultureInfo.CurrentUICulture.Name.StartsWith("en", StringComparison.OrdinalIgnoreCase); + public override string ToString() => $"{Severity}, {Message}, {FilePath}@{LinePositionSpan}"; + } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs index 4f055c5..fcc2195 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs @@ -79,11 +79,8 @@ namespace System.Text.Json.SourceGeneration.UnitTests using var emitStream = new MemoryStream(); using var xmlStream = new MemoryStream(); var result = sourceGenResult.NewCompilation.Emit(emitStream, xmlDocumentationStream: xmlStream); - var diagnostics = result.Diagnostics; - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, diagnostics, Array.Empty<(Location, string)>()); + Assert.Empty(result.Diagnostics); } [Fact] @@ -129,9 +126,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, result.Diagnostics, Array.Empty<(Location, string)>()); + Assert.Empty(result.Diagnostics); } [Fact] @@ -174,32 +169,13 @@ namespace System.Text.Json.SourceGeneration.UnitTests """; MetadataReference[] additionalReferences = { - MetadataReference.CreateFromImage(campaignImage), - MetadataReference.CreateFromImage(eventImage), - }; + MetadataReference.CreateFromImage(campaignImage), + MetadataReference.CreateFromImage(eventImage), + }; Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences); - JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation); - - Location location; - if (explicitRef) - { - // Unsupported type is not in compiling assembly, but is indicated directly with [JsonSerializable], so location points to attribute application. - INamedTypeSymbol symbol = (INamedTypeSymbol)compilation.GetSymbolsWithName("JsonContext").FirstOrDefault(); - SyntaxReference syntaxReference = symbol.GetAttributes().First().ApplicationSyntaxReference; - TextSpan textSpan = syntaxReference.Span; - location = syntaxReference.SyntaxTree.GetLocation(textSpan)!; - } - else - { - // Unsupported type is not in compiling assembly, and isn't indicated directly with [JsonSerializable], so location points to context type. - location = compilation.GetSymbolsWithName("JsonContext").First().Locations[0]; - } - - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, result.Diagnostics, Array.Empty<(Location, string)>()); + Assert.Empty(result.Diagnostics); } RunTest(explicitRef: true); @@ -218,22 +194,18 @@ namespace System.Text.Json.SourceGeneration.UnitTests TextSpan textSpan = syntaxReference.Span; Location location = syntaxReference.SyntaxTree.GetLocation(textSpan)!; - (Location, string)[] expectedWarningDiagnostics = new (Location, string)[] + var expectedDiagnostics = new DiagnosticData[] { - (location, "There are multiple types named Location. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.") + new(DiagnosticSeverity.Warning, location, "There are multiple types named Location. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.") }; - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, result.Diagnostics, expectedWarningDiagnostics); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, result.Diagnostics, Array.Empty<(Location, string)>()); + CompilationHelper.AssertEqualDiagnosticMessages(expectedDiagnostics, result.Diagnostics); // With resolution. compilation = CompilationHelper.CreateRepeatedLocationsWithResolutionCompilation(); result = CompilationHelper.RunJsonSourceGenerator(compilation); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, result.Diagnostics, Array.Empty<(Location, string)>()); + Assert.Empty(result.Diagnostics); } [Fact] @@ -255,9 +227,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests Compilation compilation = CompilationHelper.CreateCompilation(source); JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, result.Diagnostics, Array.Empty<(Location, string)>()); + Assert.Empty(result.Diagnostics); // With STJ usage. source = """ @@ -275,9 +245,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests compilation = CompilationHelper.CreateCompilation(source); result = CompilationHelper.RunJsonSourceGenerator(compilation); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, result.Diagnostics, Array.Empty<(Location, string)>()); + Assert.Empty(result.Diagnostics); } [Fact] @@ -286,9 +254,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests Compilation compilation = CompilationHelper.CreateCompilationWithInitOnlyProperties(); JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, result.Diagnostics, Array.Empty<(Location, string)>()); + Assert.Empty(result.Diagnostics); } [Fact] @@ -297,9 +263,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests Compilation compilation = CompilationHelper.CreateCompilationWithConstructorInitOnlyProperties(); JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, result.Diagnostics, Array.Empty<(Location, string)>()); + Assert.Empty(result.Diagnostics); } [Fact] @@ -308,9 +272,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests Compilation compilation = CompilationHelper.CreateCompilationWithMixedInitOnlyProperties(); JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, result.Diagnostics, Array.Empty<(Location, string)>()); + Assert.Empty(result.Diagnostics); } [Fact] @@ -319,9 +281,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests Compilation compilation = CompilationHelper.CreateCompilationWithRecordPositionalParameters(); JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, result.Diagnostics, Array.Empty<(Location, string)>()); + Assert.Empty(result.Diagnostics); } [Fact] @@ -330,9 +290,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests Compilation compilation = CompilationHelper.CreateCompilationWithRequiredProperties(); JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, result.Diagnostics, Array.Empty<(Location, string)>()); + Assert.Empty(result.Diagnostics); } [Fact] @@ -347,18 +305,16 @@ namespace System.Text.Json.SourceGeneration.UnitTests Location internalFieldLocation = compilation.GetSymbolsWithName("internalField").First().Locations[0]; Location privateFieldLocation = compilation.GetSymbolsWithName("privateField").First().Locations[0]; - (Location, string)[] expectedWarningDiagnostics = new (Location, string)[] + var expectedDiagnostics = new DiagnosticData[] { - (idLocation, "The member 'Location.Id' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), - (address2Location, "The member 'Location.Address2' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), - (countryLocation, "The member 'Location.Country' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), - (internalFieldLocation, "The member 'Location.internalField' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), - (privateFieldLocation, "The member 'Location.privateField' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), + new(DiagnosticSeverity.Warning, idLocation, "The member 'Location.Id' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), + new(DiagnosticSeverity.Warning, address2Location, "The member 'Location.Address2' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), + new(DiagnosticSeverity.Warning, countryLocation, "The member 'Location.Country' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), + new(DiagnosticSeverity.Warning, internalFieldLocation, "The member 'Location.internalField' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), + new(DiagnosticSeverity.Warning, privateFieldLocation, "The member 'Location.privateField' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), }; - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, result.Diagnostics, expectedWarningDiagnostics, sort: false); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, result.Diagnostics, Array.Empty<(Location, string)>()); + CompilationHelper.AssertEqualDiagnosticMessages(expectedDiagnostics, result.Diagnostics); } [Fact] @@ -369,14 +325,30 @@ namespace System.Text.Json.SourceGeneration.UnitTests Location myBaseClassLocation = compilation.GetSymbolsWithName("MyBaseClass").First().Locations[0]; - (Location, string)[] expectedWarningDiagnostics = new (Location, string)[] + var expectedDiagnostics = new DiagnosticData[] + { + new(DiagnosticSeverity.Warning, myBaseClassLocation, "Type 'HelloWorld.MyBaseClass' is annotated with 'JsonDerivedTypeAttribute' which is not supported in 'JsonSourceGenerationMode.Serialization'."), + }; + + CompilationHelper.AssertEqualDiagnosticMessages(expectedDiagnostics, result.Diagnostics); + } + + [Fact] + public void JsonStringEnumConverterWarns() + { + Compilation compilation = CompilationHelper.CreateTypesAnnotatedWithJsonStringEnumConverter(); + JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation); + + Location enum2PropLocation = compilation.GetSymbolsWithName("Enum2Prop").First().Locations[0]; + Location enum1TypeLocation = compilation.GetSymbolsWithName("Enum1").First().Locations[0]; + + var expectedDiagnostics = new DiagnosticData[] { - (myBaseClassLocation, "Type 'HelloWorld.MyBaseClass' is annotated with 'JsonDerivedTypeAttribute' which is not supported in 'JsonSourceGenerationMode.Serialization'."), + new(DiagnosticSeverity.Warning, enum1TypeLocation, "The member 'HelloWorld.Enum1' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter' instead."), + new(DiagnosticSeverity.Warning, enum2PropLocation, "The member 'HelloWorld.MyClass.Enum2Prop' has been annotated with 'JsonStringEnumConverter' which is not supported in native AOT. Consider using the generic 'JsonStringEnumConverter' instead."), }; - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, result.Diagnostics, expectedWarningDiagnostics, sort: false); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, result.Diagnostics, Array.Empty<(Location, string)>()); + CompilationHelper.AssertEqualDiagnosticMessages(expectedDiagnostics, result.Diagnostics); } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs index 749673c..e511ca4 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs @@ -230,9 +230,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests Assert.Empty(result.AllGeneratedTypes); } - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, result.Diagnostics, Array.Empty<(Location, string)>()); + Assert.Empty(result.Diagnostics); } [Theory] @@ -262,10 +260,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation); Assert.Empty(result.AllGeneratedTypes); - - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, result.Diagnostics, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, result.Diagnostics, Array.Empty<(Location, string)>()); + Assert.Empty(result.Diagnostics); } [Fact] @@ -602,9 +597,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests ImmutableArray generatorDiags = result.NewCompilation.GetDiagnostics(); // No diagnostics expected. - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>()); + Assert.Empty(result.Diagnostics); } [Fact] @@ -632,9 +625,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests ImmutableArray generatorDiags = result.NewCompilation.GetDiagnostics(); // No diagnostics expected. - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>()); + Assert.Empty(result.Diagnostics); } [Fact] @@ -695,9 +686,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests ImmutableArray generatorDiags = result.NewCompilation.GetDiagnostics(); // No diagnostics expected. - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, Array.Empty<(Location, string)>()); - CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>()); + Assert.Empty(result.Diagnostics); } [Fact] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.BadConverters.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.BadConverters.cs index 062a7e9..adceb7d 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.BadConverters.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.BadConverters.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Reflection; using Xunit; namespace System.Text.Json.Serialization.Tests @@ -113,44 +114,60 @@ namespace System.Text.Json.Serialization.Tests public ICollection MyEnumValues { get; set; } } + private class InvalidEnumTypeConverterClass + { + [JsonConverter(typeof(JsonStringEnumConverter))] + public InvalidTypeConverterEnum MyEnumValues { get; set; } + } + private enum InvalidTypeConverterEnum { Value1, Value2, } - [Fact] - public static void AttributeOnPropertyFail() + [Theory] + [InlineData(typeof(InvalidTypeConverterClass), + "System.Text.Json.Serialization.Tests.CustomConverterTests+InvalidTypeConverterClass.MyEnumValues", + "System.Collections.Generic.ICollection`1[System.Text.Json.Serialization.Tests.CustomConverterTests+InvalidTypeConverterEnum]")] + [InlineData(typeof(InvalidEnumTypeConverterClass), + "System.Text.Json.Serialization.Tests.CustomConverterTests+InvalidEnumTypeConverterClass.MyEnumValues", + "System.Text.Json.Serialization.Tests.CustomConverterTests+InvalidTypeConverterEnum")] + public static void AttributeOnPropertyFail(Type type, string propertyName, string propertyTypeName) { + object value = Activator.CreateInstance(type); InvalidOperationException ex; - ex = Assert.Throws(() => JsonSerializer.Serialize(new InvalidTypeConverterClass())); + ex = Assert.Throws(() => JsonSerializer.Serialize(value, type)); // Message should be in the form "The converter specified on 'System.Text.Json.Serialization.Tests.CustomConverterTests+InvalidTypeConverterClass.MyEnumValues' is not compatible with the type 'System.Collections.Generic.ICollection`1[System.Text.Json.Serialization.Tests.CustomConverterTests+InvalidTypeConverterEnum]'." - Assert.Contains("'System.Text.Json.Serialization.Tests.CustomConverterTests+InvalidTypeConverterClass.MyEnumValues'", ex.Message); + Assert.Contains($"'{propertyName}'", ex.Message); - ex = Assert.Throws(() => JsonSerializer.Deserialize("{}")); - Assert.Contains("'System.Text.Json.Serialization.Tests.CustomConverterTests+InvalidTypeConverterClass.MyEnumValues'", ex.Message); - Assert.Contains("'System.Collections.Generic.ICollection`1[System.Text.Json.Serialization.Tests.CustomConverterTests+InvalidTypeConverterEnum]'", ex.Message); + ex = Assert.Throws(() => JsonSerializer.Deserialize("{}", type)); + Assert.Contains($"'{propertyName}'", ex.Message); + Assert.Contains($"'{propertyTypeName}'", ex.Message); } [JsonConverter(typeof(JsonStringEnumConverter))] private class InvalidTypeConverterClassWithAttribute { } - [Fact] - public static void AttributeOnClassFail() - { - const string expectedSubStr = "'System.Text.Json.Serialization.Tests.CustomConverterTests+InvalidTypeConverterClassWithAttribute'"; + [JsonConverter(typeof(JsonStringEnumConverter))] + private enum InvalidTypeConverterEnumWithAttribute { } + [Theory] + [InlineData(typeof(InvalidTypeConverterClassWithAttribute), "System.Text.Json.Serialization.Tests.CustomConverterTests+InvalidTypeConverterClassWithAttribute")] + [InlineData(typeof(InvalidTypeConverterEnumWithAttribute), "System.Text.Json.Serialization.Tests.CustomConverterTests+InvalidTypeConverterEnumWithAttribute")] + public static void AttributeOnClassFail(Type type, string expectedSubStr) + { InvalidOperationException ex; + object value = Activator.CreateInstance(type); - ex = Assert.Throws(() => JsonSerializer.Serialize(new InvalidTypeConverterClassWithAttribute())); - // Message should be in the form "The converter specified on 'System.Text.Json.Serialization.Tests.CustomConverterTests+InvalidTypeConverterClassWithAttribute' is not compatible with the type 'System.Text.Json.Serialization.Tests.CustomConverterTests+InvalidTypeConverterClassWithAttribute'." + ex = Assert.Throws(() => JsonSerializer.Serialize(value, type)); int pos = ex.Message.IndexOf(expectedSubStr); Assert.True(pos > 0); Assert.Contains(expectedSubStr, ex.Message.Substring(pos + expectedSubStr.Length)); // The same string is repeated again. - ex = Assert.Throws(() => JsonSerializer.Deserialize("{}")); + ex = Assert.Throws(() => JsonSerializer.Deserialize("{}", type)); pos = ex.Message.IndexOf(expectedSubStr); Assert.True(pos > 0); Assert.Contains(expectedSubStr, ex.Message.Substring(pos + expectedSubStr.Length)); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/EnumConverterTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/EnumConverterTests.cs index ddf0e5f..2c7516f 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/EnumConverterTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/EnumConverterTests.cs @@ -13,11 +13,49 @@ namespace System.Text.Json.Serialization.Tests { public class EnumConverterTests { - [Fact] - public void ConvertDayOfWeek() + [Theory] + [InlineData(typeof(JsonStringEnumConverter), typeof(DayOfWeek))] + [InlineData(typeof(JsonStringEnumConverter), typeof(MyCustomEnum))] + [InlineData(typeof(JsonStringEnumConverter), typeof(DayOfWeek))] + [InlineData(typeof(JsonStringEnumConverter), typeof(MyCustomEnum))] + public static void JsonStringEnumConverter_SupportedType_WorksAsExpected(Type converterType, Type supportedType) + { + var options = new JsonSerializerOptions(); + var factory = (JsonConverterFactory)Activator.CreateInstance(converterType); + + Assert.True(factory.CanConvert(supportedType)); + + JsonConverter converter = factory.CreateConverter(supportedType, options); + // TODO use https://github.com/dotnet/runtime/issues/63898 once implemented + Type expectedConverterType = typeof(JsonConverter<>).MakeGenericType(supportedType); + Assert.IsAssignableFrom(expectedConverterType, converter); + } + + [Theory] + [InlineData(typeof(JsonStringEnumConverter), typeof(int))] + [InlineData(typeof(JsonStringEnumConverter), typeof(string))] + [InlineData(typeof(JsonStringEnumConverter), typeof(JsonStringEnumConverter))] + [InlineData(typeof(JsonStringEnumConverter), typeof(int))] + [InlineData(typeof(JsonStringEnumConverter), typeof(string))] + [InlineData(typeof(JsonStringEnumConverter), typeof(JsonStringEnumConverter))] + [InlineData(typeof(JsonStringEnumConverter), typeof(MyCustomEnum))] + [InlineData(typeof(JsonStringEnumConverter), typeof(DayOfWeek))] + public static void JsonStringEnumConverter_InvalidType_ThrowsArgumentOutOfRangeException(Type converterType, Type unsupportedType) + { + var options = new JsonSerializerOptions(); + var factory = (JsonConverterFactory)Activator.CreateInstance(converterType); + + Assert.False(factory.CanConvert(unsupportedType)); + ArgumentOutOfRangeException ex = Assert.Throws(() => factory.CreateConverter(unsupportedType, options)); + Assert.Contains(unsupportedType.FullName, ex.Message); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ConvertDayOfWeek(bool useGenericVariant) { - JsonSerializerOptions options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter()); + JsonSerializerOptions options = CreateStringEnumOptionsForType(useGenericVariant); WhenClass when = JsonSerializer.Deserialize(@"{""Day"":""Monday""}", options); Assert.Equal(DayOfWeek.Monday, when.Day); @@ -36,8 +74,7 @@ namespace System.Text.Json.Serialization.Tests Assert.Equal(@"""Friday""", json); // Try a unique naming policy - options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter(new ToLowerNamingPolicy())); + options = CreateStringEnumOptionsForType(useGenericVariant, new ToLowerNamingPolicy()); json = JsonSerializer.Serialize(DayOfWeek.Friday, options); Assert.Equal(@"""friday""", json); @@ -47,8 +84,7 @@ namespace System.Text.Json.Serialization.Tests Assert.Equal(@"-1", json); // Not permitting integers should throw - options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter(allowIntegerValues: false)); + options = CreateStringEnumOptionsForType(useGenericVariant, allowIntegerValues: false); Assert.Throws(() => JsonSerializer.Serialize((DayOfWeek)(-1), options)); } @@ -62,11 +98,12 @@ namespace System.Text.Json.Serialization.Tests public DayOfWeek Day { get; set; } } - [Fact] - public void ConvertFileAttributes() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ConvertFileAttributes(bool useGenericVariant) { - JsonSerializerOptions options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter()); + JsonSerializerOptions options = CreateStringEnumOptionsForType(useGenericVariant); FileState state = JsonSerializer.Deserialize(@"{""Attributes"":""ReadOnly""}", options); Assert.Equal(FileAttributes.ReadOnly, state.Attributes); @@ -95,8 +132,7 @@ namespace System.Text.Json.Serialization.Tests Assert.Equal(@"""Temporary, Offline""", json); // Try a unique casing - options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter(new ToLowerNamingPolicy())); + options = CreateStringEnumOptionsForType(useGenericVariant, new ToLowerNamingPolicy()); json = JsonSerializer.Serialize(FileAttributes.NoScrubData, options); Assert.Equal(@"""noscrubdata""", json); @@ -108,13 +144,11 @@ namespace System.Text.Json.Serialization.Tests Assert.Equal(@"-1", json); // Not permitting integers should throw - options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter(allowIntegerValues: false)); + options = CreateStringEnumOptionsForType(useGenericVariant, allowIntegerValues: false); Assert.Throws(() => JsonSerializer.Serialize((FileAttributes)(-1), options)); // Flag values honor naming policy correctly - options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter(new SimpleSnakeCasePolicy())); + options = CreateStringEnumOptionsForType(useGenericVariant, new SimpleSnakeCasePolicy()); json = JsonSerializer.Serialize( FileAttributes.Directory | FileAttributes.Compressed | FileAttributes.IntegrityStream, @@ -140,6 +174,11 @@ namespace System.Text.Json.Serialization.Tests public DayOfWeek WorkEnd { get; set; } [JsonConverter(typeof(LowerCaseEnumConverter))] public DayOfWeek WeekEnd { get; set; } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public DayOfWeek WorkStart2 { get; set; } + [JsonConverter(typeof(LowerCaseEnumConverter))] + public DayOfWeek WeekEnd2 { get; set; } } private class LowerCaseEnumConverter : JsonStringEnumConverter @@ -149,17 +188,34 @@ namespace System.Text.Json.Serialization.Tests } } + private class LowerCaseEnumConverter : JsonStringEnumConverter + where TEnum : struct, Enum + { + public LowerCaseEnumConverter() : base(new ToLowerNamingPolicy()) + { + } + } + [Fact] public void ConvertEnumUsingAttributes() { - Week week = new Week { WorkStart = DayOfWeek.Monday, WorkEnd = DayOfWeek.Friday, WeekEnd = DayOfWeek.Saturday }; + Week week = new Week { + WorkStart = DayOfWeek.Monday, + WorkEnd = DayOfWeek.Friday, + WeekEnd = DayOfWeek.Saturday, + WorkStart2 = DayOfWeek.Tuesday, + WeekEnd2 = DayOfWeek.Thursday, + }; + string json = JsonSerializer.Serialize(week); - Assert.Equal(@"{""WorkStart"":""Monday"",""WorkEnd"":5,""WeekEnd"":""saturday""}", json); + Assert.Equal("""{"WorkStart":"Monday","WorkEnd":5,"WeekEnd":"saturday","WorkStart2":"Tuesday","WeekEnd2":"thursday"}""", json); week = JsonSerializer.Deserialize(json); Assert.Equal(DayOfWeek.Monday, week.WorkStart); Assert.Equal(DayOfWeek.Friday, week.WorkEnd); Assert.Equal(DayOfWeek.Saturday, week.WeekEnd); + Assert.Equal(DayOfWeek.Tuesday, week.WorkStart2); + Assert.Equal(DayOfWeek.Thursday, week.WeekEnd2); } [Fact] @@ -190,26 +246,34 @@ namespace System.Text.Json.Serialization.Tests Second = 2 } - [Fact] - public void EnumWithConverterAttribute() + [JsonConverter(typeof(JsonStringEnumConverter))] + private enum MyCustomEnum2 { - string json = JsonSerializer.Serialize(MyCustomEnum.Second); - Assert.Equal(@"""Second""", json); + First = 1, + Second = 2 + } - MyCustomEnum obj = JsonSerializer.Deserialize("\"Second\""); - Assert.Equal(MyCustomEnum.Second, obj); + [Theory] + [InlineData(typeof(MyCustomEnum), MyCustomEnum.Second, "\"Second\"", "2")] + [InlineData(typeof(MyCustomEnum2), MyCustomEnum2.Second, "\"Second\"", "2")] + public void EnumWithConverterAttribute(Type enumType, object value, string expectedJson, string alternativeJson) + { + string json = JsonSerializer.Serialize(value, enumType); + Assert.Equal(expectedJson, json); + + object? result = JsonSerializer.Deserialize(json, enumType); + Assert.Equal(value, result); - obj = JsonSerializer.Deserialize("2"); - Assert.Equal(MyCustomEnum.Second, obj); + result = JsonSerializer.Deserialize(alternativeJson, enumType); + Assert.Equal(value, result); } - [Fact] - public static void EnumWithNoValues() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void EnumWithNoValues(bool useGenericVariant) { - var options = new JsonSerializerOptions - { - Converters = { new JsonStringEnumConverter() } - }; + JsonSerializerOptions options = CreateStringEnumOptionsForType(useGenericVariant); Assert.Equal("-1", JsonSerializer.Serialize((EmptyEnum)(-1), options)); Assert.Equal("1", JsonSerializer.Serialize((EmptyEnum)(1), options)); @@ -217,13 +281,12 @@ namespace System.Text.Json.Serialization.Tests public enum EmptyEnum { }; - [Fact] - public static void MoreThan64EnumValuesToSerialize() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void MoreThan64EnumValuesToSerialize(bool useGenericVariant) { - var options = new JsonSerializerOptions - { - Converters = { new JsonStringEnumConverter() } - }; + JsonSerializerOptions options = CreateStringEnumOptionsForType(useGenericVariant); for (int i = 0; i < 128; i++) { @@ -234,13 +297,12 @@ namespace System.Text.Json.Serialization.Tests } } - [Fact] - public static void MoreThan64EnumValuesToSerializeWithNamingPolicy() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void MoreThan64EnumValuesToSerializeWithNamingPolicy(bool useGenericVariant) { - var options = new JsonSerializerOptions - { - Converters = { new JsonStringEnumConverter(new ToLowerNamingPolicy()) } - }; + JsonSerializerOptions options = CreateStringEnumOptionsForType(useGenericVariant, new ToLowerNamingPolicy()); for (int i = 0; i < 128; i++) { @@ -556,16 +618,12 @@ namespace System.Text.Json.Serialization.Tests G = 1 << 6, } - [Fact] - public static void Honor_EnumNamingPolicy_On_Deserialization() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Honor_EnumNamingPolicy_On_Deserialization(bool useGenericVariant) { - JsonSerializerOptions options = new() - { - Converters = - { - new JsonStringEnumConverter(namingPolicy: new SimpleSnakeCasePolicy() ) - } - }; + JsonSerializerOptions options = CreateStringEnumOptionsForType(useGenericVariant, new SimpleSnakeCasePolicy()); BindingFlags bindingFlags = JsonSerializer.Deserialize(@"""non_public""", options); Assert.Equal(BindingFlags.NonPublic, bindingFlags); @@ -585,18 +643,14 @@ namespace System.Text.Json.Serialization.Tests Assert.Null(JsonSerializer.Deserialize("null", options)); } - [Fact] - public static void EnumDictionaryKeyDeserialization() + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void EnumDictionaryKeyDeserialization(bool useGenericVariant) { JsonNamingPolicy snakeCasePolicy = new SimpleSnakeCasePolicy(); - JsonSerializerOptions options = new() - { - Converters = - { - new JsonStringEnumConverter(namingPolicy: snakeCasePolicy) - }, - DictionaryKeyPolicy = snakeCasePolicy - }; + JsonSerializerOptions options = CreateStringEnumOptionsForType(useGenericVariant); + options.DictionaryKeyPolicy = snakeCasePolicy; // Baseline. var dict = JsonSerializer.Deserialize>(@"{""NonPublic, Public"": 1}", options); @@ -635,5 +689,18 @@ namespace System.Text.Json.Serialization.Tests { public override string ConvertName(string name) => name + "0"; } + + private static JsonSerializerOptions CreateStringEnumOptionsForType(bool useGenericVariant, JsonNamingPolicy? namingPolicy = null, bool allowIntegerValues = true) where TEnum : struct, Enum + { + return new JsonSerializerOptions + { + Converters = + { + useGenericVariant + ? new JsonStringEnumConverter(namingPolicy, allowIntegerValues) + : new JsonStringEnumConverter(namingPolicy, allowIntegerValues) + } + }; + } } } diff --git a/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml b/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml index 1d49796..e3cd7e6 100644 --- a/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml +++ b/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml @@ -2641,4 +2641,10 @@ net7.0/System.Runtime.Loader.dll net8.0/System.Runtime.Loader.dll + + CP0015 + T:System.Text.Json.Serialization.JsonStringEnumConverter:[T:System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute] + net7.0/System.Text.Json.dll + net8.0/System.Text.Json.dll + \ No newline at end of file