From: Layomi Akinrinade Date: Fri, 25 Jun 2021 23:09:23 +0000 (-0700) Subject: Clean up JSON source gen APIs (#54527) X-Git-Tag: submit/tizen/20210909.063632~569 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=118c530033b43d47fcabab00efce414d5e4fd19c;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Clean up JSON source gen APIs (#54527) * Clean up JSON source gen APIs * Address review feedback * Simplify GenerateX computation --- diff --git a/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs b/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs index 9929071..10a4be3 100644 --- a/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs +++ b/src/libraries/System.Text.Json/Common/JsonKnownNamingPolicy.cs @@ -21,6 +21,6 @@ namespace System.Text.Json.Serialization /// /// Specifies that the built-in be used to convert JSON property names. /// - BuiltInCamelCase = 1 + CamelCase = 1 } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonSerializableAttribute.cs b/src/libraries/System.Text.Json/Common/JsonSerializableAttribute.cs similarity index 79% rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonSerializableAttribute.cs rename to src/libraries/System.Text.Json/Common/JsonSerializableAttribute.cs index b223fad..1b689ba 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonSerializableAttribute.cs +++ b/src/libraries/System.Text.Json/Common/JsonSerializableAttribute.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#if !BUILDING_SOURCE_GENERATOR using System.Text.Json.Serialization.Metadata; +#endif namespace System.Text.Json.Serialization { @@ -10,7 +12,13 @@ namespace System.Text.Json.Serialization /// when serializing and deserializing instances of the specified type and types in its object graph. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public sealed class JsonSerializableAttribute : JsonAttribute + +#if BUILDING_SOURCE_GENERATOR + internal +#else + public +#endif + sealed class JsonSerializableAttribute : JsonAttribute { /// /// Initializes a new instance of with the specified type. @@ -28,7 +36,8 @@ namespace System.Text.Json.Serialization public string? TypeInfoPropertyName { get; set; } /// - /// Determines what the source generator should generate for the type. + /// Determines what the source generator should generate for the type. If the value is , + /// then the setting specified on will be used. /// public JsonSourceGenerationMode GenerationMode { get; set; } } diff --git a/src/libraries/System.Text.Json/Common/JsonSourceGenerationMode.cs b/src/libraries/System.Text.Json/Common/JsonSourceGenerationMode.cs index b46292b..99785d8 100644 --- a/src/libraries/System.Text.Json/Common/JsonSourceGenerationMode.cs +++ b/src/libraries/System.Text.Json/Common/JsonSourceGenerationMode.cs @@ -15,13 +15,11 @@ namespace System.Text.Json.Serialization enum JsonSourceGenerationMode { /// - /// Instructs the JSON source generator to generate serialization logic and type metadata to fallback to - /// when the run-time options are not compatible with the indicated . + /// When specified on , indicates that both type-metadata initialization logic + /// and optimized serialization logic should be generated for all types. When specified on , + /// indicates that the setting on should be used. /// - /// - /// This mode supports all features. - /// - MetadataAndSerialization = 0, + Default = 0, /// /// Instructs the JSON source generator to generate type-metadata initialization logic. @@ -32,7 +30,7 @@ namespace System.Text.Json.Serialization Metadata = 1, /// - /// Instructs the JSON source generator to generate serialization logic. + /// Instructs the JSON source generator to generate optimized serialization logic. /// /// /// This mode supports only a subset of features. diff --git a/src/libraries/System.Text.Json/Common/JsonSerializerOptionsAttribute.cs b/src/libraries/System.Text.Json/Common/JsonSourceGenerationOptionsAttribute.cs similarity index 80% rename from src/libraries/System.Text.Json/Common/JsonSerializerOptionsAttribute.cs rename to src/libraries/System.Text.Json/Common/JsonSourceGenerationOptionsAttribute.cs index a1b4d09..334e522 100644 --- a/src/libraries/System.Text.Json/Common/JsonSerializerOptionsAttribute.cs +++ b/src/libraries/System.Text.Json/Common/JsonSourceGenerationOptionsAttribute.cs @@ -13,7 +13,7 @@ namespace System.Text.Json.Serialization #else public #endif - class JsonSerializerOptionsAttribute : JsonAttribute + class JsonSourceGenerationOptionsAttribute : JsonAttribute { /// /// Specifies the default ignore condition. @@ -43,11 +43,16 @@ namespace System.Text.Json.Serialization /// /// Specifies a built-in naming polices to convert JSON property names with. /// - public JsonKnownNamingPolicy NamingPolicy { get; set; } + public JsonKnownNamingPolicy PropertyNamingPolicy { get; set; } /// /// Specifies whether JSON output should be pretty-printed. /// public bool WriteIndented { get; set; } + + /// + /// Specifies the source generation mode for types that don't explicitly set the mode with . + /// + public JsonSourceGenerationMode GenerationMode { get; set; } } } diff --git a/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs b/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs index 7ba239f..f4b27c2 100644 --- a/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs @@ -13,11 +13,11 @@ namespace System.Text.Json.SourceGeneration /// internal sealed class ContextGenerationSpec { - public JsonSerializerOptionsAttribute SerializerOptions { get; init; } + public JsonSourceGenerationOptionsAttribute GenerationOptions { get; init; } public Type ContextType { get; init; } - public List? RootSerializableTypes { get; init; } + public List RootSerializableTypes { get; } = new(); public List ContextClassDeclarationList { get; init; } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 251aa2a..e00a0ec 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -637,10 +637,10 @@ private static {JsonPropertyInfoTypeRef}[] {propInitMethodName}({JsonSerializerC bool canBeNull, List? properties) { - JsonSerializerOptionsAttribute options = _currentContext.SerializerOptions; + JsonSourceGenerationOptionsAttribute options = _currentContext.GenerationOptions; // Add the property names to the context-wide cache; we'll generate the source to initialize them at the end of generation. - string[] runtimePropNames = GetRuntimePropNames(properties, options.NamingPolicy); + string[] runtimePropNames = GetRuntimePropNames(properties, options.PropertyNamingPolicy); _currentContext.RuntimePropertyNames.UnionWith(runtimePropNames); StringBuilder sb = new(); @@ -855,7 +855,7 @@ private static void {serializeMethodName}({Utf8JsonWriterTypeRef} {WriterVarName { runtimePropName = jsonPropName; } - else if (namingPolicy == JsonKnownNamingPolicy.BuiltInCamelCase) + else if (namingPolicy == JsonKnownNamingPolicy.CamelCase) { runtimePropName = JsonNamingPolicy.CamelCase.ConvertName(clrPropName); } @@ -890,7 +890,7 @@ public {typeInfoPropertyTypeRef} {typeFriendlyName} private string WrapWithCheckForCustomConverterIfRequired(string source, string typeCompilableName, string typeFriendlyName, string numberHandlingNamedArg) { - if (_currentContext.SerializerOptions.IgnoreRuntimeCustomConverters) + if (_currentContext.GenerationOptions.IgnoreRuntimeCustomConverters) { return source; } @@ -933,9 +933,9 @@ public {contextTypeName}({JsonSerializerOptionsTypeRef} options) : base(options, private string GetLogicForDefaultSerializerOptionsInit() { - JsonSerializerOptionsAttribute options = _currentContext.SerializerOptions; + JsonSourceGenerationOptionsAttribute options = _currentContext.GenerationOptions; - string? namingPolicyInit = options.NamingPolicy == JsonKnownNamingPolicy.BuiltInCamelCase + string? namingPolicyInit = options.PropertyNamingPolicy == JsonKnownNamingPolicy.CamelCase ? $@" PropertyNamingPolicy = {JsonNamingPolicyTypeRef}.CamelCase" : null; @@ -953,7 +953,7 @@ private static {JsonSerializerOptionsTypeRef} {DefaultOptionsStaticVarName} {{ g private string GetFetchLogicForRuntimeSpecifiedCustomConverter() { - if (_currentContext.SerializerOptions.IgnoreRuntimeCustomConverters) + if (_currentContext.GenerationOptions.IgnoreRuntimeCustomConverters) { return ""; } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index b909e1e..386a799 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -98,9 +98,9 @@ namespace System.Text.Json.SourceGeneration Compilation compilation = _executionContext.Compilation; INamedTypeSymbol jsonSerializerContextSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializerContext"); INamedTypeSymbol jsonSerializableAttributeSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializableAttribute"); - INamedTypeSymbol jsonSerializerOptionsAttributeSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializerOptionsAttribute"); + INamedTypeSymbol jsonSourceGenerationOptionsAttributeSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSourceGenerationOptionsAttribute"); - if (jsonSerializerContextSymbol == null || jsonSerializableAttributeSymbol == null || jsonSerializerOptionsAttributeSymbol == null) + if (jsonSerializerContextSymbol == null || jsonSerializableAttributeSymbol == null || jsonSourceGenerationOptionsAttributeSymbol == null) { return null; } @@ -117,8 +117,8 @@ namespace System.Text.Json.SourceGeneration continue; } - List? rootTypes = null; - JsonSerializerOptionsAttribute? options = null; + JsonSourceGenerationOptionsAttribute? options = null; + List? serializableAttributeList = null; foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntax.AttributeLists) { @@ -133,19 +133,15 @@ namespace System.Text.Json.SourceGeneration if (jsonSerializableAttributeSymbol.Equals(attributeContainingTypeSymbol, SymbolEqualityComparer.Default)) { - TypeGenerationSpec? metadata = GetRootSerializableType(compilationSemanticModel, attributeSyntax); - if (metadata != null) - { - (rootTypes ??= new List()).Add(metadata); - } + (serializableAttributeList ??= new List()).Add(attributeSyntax); } - else if (jsonSerializerOptionsAttributeSymbol.Equals(attributeContainingTypeSymbol, SymbolEqualityComparer.Default)) + else if (jsonSourceGenerationOptionsAttributeSymbol.Equals(attributeContainingTypeSymbol, SymbolEqualityComparer.Default)) { options = GetSerializerOptions(attributeSyntax); } } - if (rootTypes == null) + if (serializableAttributeList == null) { // No types were indicated with [JsonSerializable] continue; @@ -161,14 +157,29 @@ namespace System.Text.Json.SourceGeneration continue; } - contextGenSpecList ??= new List(); - contextGenSpecList.Add(new ContextGenerationSpec + ContextGenerationSpec contextGenSpec = new() { - SerializerOptions = options ?? new JsonSerializerOptionsAttribute(), + GenerationOptions = options ?? new JsonSourceGenerationOptionsAttribute(), ContextType = contextTypeSymbol.AsType(_metadataLoadContext), - RootSerializableTypes = rootTypes, ContextClassDeclarationList = classDeclarationList - }); + }; + + foreach(AttributeSyntax attribute in serializableAttributeList) + { + TypeGenerationSpec? metadata = GetRootSerializableType(compilationSemanticModel, attribute, contextGenSpec.GenerationOptions.GenerationMode); + if (metadata != null) + { + contextGenSpec.RootSerializableTypes.Add(metadata); + } + } + + if (contextGenSpec.RootSerializableTypes.Count == 0) + { + continue; + } + + contextGenSpecList ??= new List(); + contextGenSpecList.Add(contextGenSpec); // Clear the cache of generated metadata between the processing of context classes. _typeGenerationSpecCache.Clear(); @@ -220,11 +231,10 @@ namespace System.Text.Json.SourceGeneration return match != null; } - private static bool TryGetClassDeclarationList(INamedTypeSymbol typeSymbol, [NotNullWhenAttribute(true)] out List classDeclarationList) + private static bool TryGetClassDeclarationList(INamedTypeSymbol typeSymbol, [NotNullWhenAttribute(true)] out List? classDeclarationList) { - classDeclarationList = new(); - INamedTypeSymbol currentSymbol = typeSymbol; + classDeclarationList = null; while (currentSymbol != null) { @@ -259,7 +269,7 @@ namespace System.Text.Json.SourceGeneration declarationElements[tokenCount] = "class"; declarationElements[tokenCount + 1] = currentSymbol.Name; - classDeclarationList.Add(string.Join(" ", declarationElements)); + (classDeclarationList ??= new List()).Add(string.Join(" ", declarationElements)); } currentSymbol = currentSymbol.ContainingType; @@ -269,13 +279,15 @@ namespace System.Text.Json.SourceGeneration return true; } - private TypeGenerationSpec? GetRootSerializableType(SemanticModel compilationSemanticModel, AttributeSyntax attributeSyntax) + private TypeGenerationSpec? GetRootSerializableType( + SemanticModel compilationSemanticModel, + AttributeSyntax attributeSyntax, + JsonSourceGenerationMode generationMode) { IEnumerable attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax); ITypeSymbol? typeSymbol = null; string? typeInfoPropertyName = null; - JsonSourceGenerationMode generationMode = default; bool seenFirstArg = false; foreach (AttributeArgumentSyntax node in attributeArguments) @@ -298,15 +310,20 @@ namespace System.Text.Json.SourceGeneration NameEqualsSyntax? propertyNameNode = childNodes.First() as NameEqualsSyntax; Debug.Assert(propertyNameNode != null); - SyntaxNode? propertyValueMode = childNodes.ElementAtOrDefault(1); - if (propertyNameNode.Name.Identifier.ValueText == "TypeInfoPropertyName") + SyntaxNode? propertyValueNode = childNodes.ElementAtOrDefault(1); + string optionName = propertyNameNode.Name.Identifier.ValueText; + + if (optionName == nameof(JsonSerializableAttribute.TypeInfoPropertyName)) { - typeInfoPropertyName = propertyValueMode.GetFirstToken().ValueText; + typeInfoPropertyName = propertyValueNode.GetFirstToken().ValueText; } - else + else if (optionName == nameof(JsonSerializableAttribute.GenerationMode)) { - Debug.Assert(propertyNameNode.Name.Identifier.ValueText == "GenerationMode"); - generationMode = (JsonSourceGenerationMode)Enum.Parse(typeof(JsonSourceGenerationMode), propertyValueMode.GetLastToken().ValueText); + JsonSourceGenerationMode? mode = GetJsonSourceGenerationModeEnumVal(propertyValueNode); + if (mode.HasValue) + { + generationMode = mode.Value; + } } } } @@ -325,37 +342,49 @@ namespace System.Text.Json.SourceGeneration return null; } - TypeGenerationSpec typeGenerationSpec = GetOrAddTypeGenerationSpec(type); + TypeGenerationSpec typeGenerationSpec = GetOrAddTypeGenerationSpec(type, generationMode); if (typeInfoPropertyName != null) { typeGenerationSpec.TypeInfoPropertyName = typeInfoPropertyName; } - ClassType classType = typeGenerationSpec.ClassType; - CollectionType collectionType = typeGenerationSpec.CollectionType; - switch (generationMode) + if (generationMode != default) { - case JsonSourceGenerationMode.MetadataAndSerialization: - break; - case JsonSourceGenerationMode.Metadata: - typeGenerationSpec.GenerateSerializationLogic = false; - break; - case JsonSourceGenerationMode.Serialization: - typeGenerationSpec.GenerateMetadata = false; - break; - default: - throw new InvalidOperationException(); + typeGenerationSpec.GenerationMode = generationMode; } return typeGenerationSpec; } - private static JsonSerializerOptionsAttribute? GetSerializerOptions(AttributeSyntax attributeSyntax) + private static JsonSourceGenerationMode? GetJsonSourceGenerationModeEnumVal(SyntaxNode propertyValueMode) { + IEnumerable enumTokens = propertyValueMode + .DescendantTokens() + .Where(token => IsValidEnumIdentifier(token.ValueText)) + .Select(token => token.ValueText); + string enumAsStr = string.Join(",", enumTokens); + + if (Enum.TryParse(enumAsStr, out JsonSourceGenerationMode value)) + { + return value; + } + + return null; + + static bool IsValidEnumIdentifier(string token) => token != nameof(JsonSourceGenerationMode) && token != "." && token != "|"; + } + + private static JsonSourceGenerationOptionsAttribute? GetSerializerOptions(AttributeSyntax? attributeSyntax) + { + if (attributeSyntax == null) + { + return null; + } + IEnumerable attributeArguments = attributeSyntax.DescendantNodes().Where(node => node is AttributeArgumentSyntax); - JsonSerializerOptionsAttribute options = new(); + JsonSourceGenerationOptionsAttribute options = new(); foreach (AttributeArgumentSyntax node in attributeArguments) { @@ -369,26 +398,70 @@ namespace System.Text.Json.SourceGeneration switch (propertyNameNode.Name.Identifier.ValueText) { - case "DefaultIgnoreCondition": - options.DefaultIgnoreCondition = (JsonIgnoreCondition)Enum.Parse(typeof(JsonIgnoreCondition), propertyValueStr); + case nameof(JsonSourceGenerationOptionsAttribute.DefaultIgnoreCondition): + { + if (Enum.TryParse(propertyValueStr, out JsonIgnoreCondition value)) + { + options.DefaultIgnoreCondition = value; + } + } break; - case "IgnoreReadOnlyFields": - options.IgnoreReadOnlyFields = bool.Parse(propertyValueStr); + case nameof(JsonSourceGenerationOptionsAttribute.IgnoreReadOnlyFields): + { + if (bool.TryParse(propertyValueStr, out bool value)) + { + options.IgnoreReadOnlyFields = value; + } + } break; - case "IgnoreReadOnlyProperties": - options.IgnoreReadOnlyProperties = bool.Parse(propertyValueStr); + case nameof(JsonSourceGenerationOptionsAttribute.IgnoreReadOnlyProperties): + { + if (bool.TryParse(propertyValueStr, out bool value)) + { + options.IgnoreReadOnlyProperties = value; + } + } + break; + case nameof(JsonSourceGenerationOptionsAttribute.IgnoreRuntimeCustomConverters): + { + if (bool.TryParse(propertyValueStr, out bool value)) + { + options.IgnoreRuntimeCustomConverters = value; + } + } break; - case "IgnoreRuntimeCustomConverters": - options.IgnoreRuntimeCustomConverters = bool.Parse(propertyValueStr); + case nameof(JsonSourceGenerationOptionsAttribute.IncludeFields): + { + if (bool.TryParse(propertyValueStr, out bool value)) + { + options.IncludeFields = value; + } + } break; - case "IncludeFields": - options.IncludeFields = bool.Parse(propertyValueStr); + case nameof(JsonSourceGenerationOptionsAttribute.PropertyNamingPolicy): + { + if (Enum.TryParse(propertyValueStr, out JsonKnownNamingPolicy value)) + { + options.PropertyNamingPolicy = value; + } + } break; - case "NamingPolicy": - options.NamingPolicy = (JsonKnownNamingPolicy)Enum.Parse(typeof(JsonKnownNamingPolicy), propertyValueStr); + case nameof(JsonSourceGenerationOptionsAttribute.WriteIndented): + { + if (bool.TryParse(propertyValueStr, out bool value)) + { + options.WriteIndented = value; + } + } break; - case "WriteIndented": - options.WriteIndented = bool.Parse(propertyValueStr); + case nameof(JsonSourceGenerationOptionsAttribute.GenerationMode): + { + JsonSourceGenerationMode? mode = GetJsonSourceGenerationModeEnumVal(propertyValueNode); + if (mode.HasValue) + { + options.GenerationMode = mode.Value; + } + } break; default: throw new InvalidOperationException(); @@ -398,7 +471,7 @@ namespace System.Text.Json.SourceGeneration return options; } - private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type) + private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGenerationMode generationMode) { if (_typeGenerationSpecCache.TryGetValue(type, out TypeGenerationSpec? typeMetadata)) { @@ -514,7 +587,7 @@ namespace System.Text.Json.SourceGeneration foreach (PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags)) { - PropertyGenerationSpec metadata = GetPropertyGenerationSpec(propertyInfo); + PropertyGenerationSpec metadata = GetPropertyGenerationSpec(propertyInfo, generationMode); // Ignore indexers. if (propertyInfo.GetIndexParameters().Length > 0) @@ -530,7 +603,7 @@ namespace System.Text.Json.SourceGeneration foreach (FieldInfo fieldInfo in currentType.GetFields(bindingFlags)) { - PropertyGenerationSpec metadata = GetPropertyGenerationSpec(fieldInfo); + PropertyGenerationSpec metadata = GetPropertyGenerationSpec(fieldInfo, generationMode); if (metadata.CanUseGetter || metadata.CanUseSetter) { @@ -541,6 +614,7 @@ namespace System.Text.Json.SourceGeneration } typeMetadata.Initialize( + generationMode, typeRef: type.GetUniqueCompilableTypeName(), typeInfoPropertyName: type.GetFriendlyTypeName(), type, @@ -549,16 +623,16 @@ namespace System.Text.Json.SourceGeneration numberHandling, propertiesMetadata, collectionType, - collectionKeyTypeMetadata: collectionKeyType != null ? GetOrAddTypeGenerationSpec(collectionKeyType) : null, - collectionValueTypeMetadata: collectionValueType != null ? GetOrAddTypeGenerationSpec(collectionValueType) : null, + collectionKeyTypeMetadata: collectionKeyType != null ? GetOrAddTypeGenerationSpec(collectionKeyType, generationMode) : null, + collectionValueTypeMetadata: collectionValueType != null ? GetOrAddTypeGenerationSpec(collectionValueType, generationMode) : null, constructionStrategy, - nullableUnderlyingTypeMetadata: nullableUnderlyingType != null ? GetOrAddTypeGenerationSpec(nullableUnderlyingType) : null, + nullableUnderlyingTypeMetadata: nullableUnderlyingType != null ? GetOrAddTypeGenerationSpec(nullableUnderlyingType, generationMode) : null, converterInstatiationLogic); return typeMetadata; } - private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo) + private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo, JsonSourceGenerationMode generationMode) { IList attributeDataList = CustomAttributeData.GetCustomAttributes(memberInfo); @@ -669,7 +743,7 @@ namespace System.Text.Json.SourceGeneration DefaultIgnoreCondition = ignoreCondition, NumberHandling = numberHandling, HasJsonInclude = hasJsonInclude, - TypeGenerationSpec = GetOrAddTypeGenerationSpec(memberCLRType), + TypeGenerationSpec = GetOrAddTypeGenerationSpec(memberCLRType, generationMode), DeclaringTypeRef = $"global::{memberInfo.DeclaringType.GetUniqueCompilableTypeName()}", ConverterInstantiationLogic = converterInstantiationLogic }; diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs index d49dfd0..0966d4e 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs @@ -33,7 +33,6 @@ namespace System.Text.Json.SourceGeneration /// public void Execute(GeneratorExecutionContext executionContext) { - //if (!Diagnostics.Debugger.IsAttached) { Diagnostics.Debugger.Launch(); }; SyntaxReceiver receiver = (SyntaxReceiver)executionContext.SyntaxReceiver; List? contextClasses = receiver.ClassDeclarationSyntaxList; if (contextClasses == null) diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj index 5ed7281..ec46f79 100644 --- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj +++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj @@ -28,8 +28,9 @@ - + + diff --git a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs index 20155f0..0d457cd 100644 --- a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs @@ -23,14 +23,11 @@ namespace System.Text.Json.SourceGeneration /// public string TypeInfoPropertyName { get; set; } - public bool GenerateMetadata { get; set; } = true; + public JsonSourceGenerationMode GenerationMode { get; set; } - private bool? _generateSerializationLogic; - public bool GenerateSerializationLogic - { - get => _generateSerializationLogic ??= FastPathIsSupported(); - set => _generateSerializationLogic = value; - } + public bool GenerateMetadata => GenerationModeIsSpecified(JsonSourceGenerationMode.Metadata); + + public bool GenerateSerializationLogic => GenerationModeIsSpecified(JsonSourceGenerationMode.Serialization) && FastPathIsSupported(); public Type Type { get; private set; } @@ -57,6 +54,7 @@ namespace System.Text.Json.SourceGeneration public string? ConverterInstantiationLogic { get; private set; } public void Initialize( + JsonSourceGenerationMode generationMode, string typeRef, string typeInfoPropertyName, Type type, @@ -71,6 +69,7 @@ namespace System.Text.Json.SourceGeneration TypeGenerationSpec? nullableUnderlyingTypeMetadata, string? converterInstantiationLogic) { + GenerationMode = generationMode; TypeRef = $"global::{typeRef}"; TypeInfoPropertyName = typeInfoPropertyName; Type = type; @@ -87,7 +86,7 @@ namespace System.Text.Json.SourceGeneration ConverterInstantiationLogic = converterInstantiationLogic; } - public bool FastPathIsSupported() + private bool FastPathIsSupported() { if (ClassType == ClassType.Object) { @@ -106,5 +105,7 @@ namespace System.Text.Json.SourceGeneration return false; } + + private bool GenerationModeIsSpecified(JsonSourceGenerationMode mode) => GenerationMode == JsonSourceGenerationMode.Default || (mode & GenerationMode) != 0; } } 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 e68bffd..f22feb4 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -792,7 +792,7 @@ namespace System.Text.Json.Serialization public enum JsonKnownNamingPolicy { Unspecified = 0, - BuiltInCamelCase = 1, + CamelCase = 1, } [System.FlagsAttribute] public enum JsonNumberHandling @@ -828,21 +828,22 @@ namespace System.Text.Json.Serialization public abstract System.Text.Json.Serialization.Metadata.JsonTypeInfo? GetTypeInfo(System.Type type); } [System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=false)] - public partial class JsonSerializerOptionsAttribute : System.Text.Json.Serialization.JsonAttribute + public partial class JsonSourceGenerationOptionsAttribute : System.Text.Json.Serialization.JsonAttribute { - public JsonSerializerOptionsAttribute() { } + public JsonSourceGenerationOptionsAttribute() { } public System.Text.Json.Serialization.JsonIgnoreCondition DefaultIgnoreCondition { get { throw null; } set { } } public bool IgnoreReadOnlyFields { get { throw null; } set { } } public bool IgnoreReadOnlyProperties { get { throw null; } set { } } public bool IgnoreRuntimeCustomConverters { get { throw null; } set { } } public bool IncludeFields { get { throw null; } set { } } - public System.Text.Json.Serialization.JsonKnownNamingPolicy NamingPolicy { get { throw null; } set { } } + public System.Text.Json.Serialization.JsonKnownNamingPolicy PropertyNamingPolicy { get { throw null; } set { } } public bool WriteIndented { get { throw null; } set { } } + public JsonSourceGenerationMode GenerationMode { get { throw null; } set { } } } [System.FlagsAttribute] public enum JsonSourceGenerationMode { - MetadataAndSerialization = 0, + Default = 0, Metadata = 1, Serialization = 2, } diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index bc957ca..142d9f8 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -25,8 +25,9 @@ - + + @@ -89,7 +90,6 @@ - diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs index 77f723b..a302d92 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs @@ -84,7 +84,7 @@ namespace System.Text.Json.Serialization /// Creates an instance of and binds it with the indicated . /// /// The run-time provided options for the context instance. - /// The default run-time options for the context. It's values are defined at design-time via . + /// The default run-time options for the context. It's values are defined at design-time via . /// /// If no instance options are passed, then no options are set until the context is bound using , /// or until is called, where a new options instance is created and bound. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Collections.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Collections.cs index 12f3717..e0f846d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Collections.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Collections.cs @@ -15,7 +15,7 @@ namespace System.Text.Json.Serialization.Metadata /// The to use. /// A instance representing the element type. /// The option to apply to number collection elements. - /// An optimized serialization implementation assuming pre-determined defaults. + /// An optimized serialization implementation assuming pre-determined defaults. /// public static JsonTypeInfo CreateArrayInfo( JsonSerializerOptions options, @@ -40,7 +40,7 @@ namespace System.Text.Json.Serialization.Metadata /// A to create an instance of the list when deserializing. /// A instance representing the element type. /// The option to apply to number collection elements. - /// An optimized serialization implementation assuming pre-determined defaults. + /// An optimized serialization implementation assuming pre-determined defaults. /// public static JsonTypeInfo CreateListInfo( JsonSerializerOptions options, @@ -69,7 +69,7 @@ namespace System.Text.Json.Serialization.Metadata /// A instance representing the key type. /// A instance representing the value type. /// The option to apply to number collection elements. - /// An optimized serialization implementation assuming pre-determined defaults. + /// An optimized serialization implementation assuming pre-determined defaults. /// public static JsonTypeInfo CreateDictionaryInfo( JsonSerializerOptions options, diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs index 14fd403..5268573 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs @@ -93,7 +93,7 @@ namespace System.Text.Json.Serialization.Metadata /// The to initialize the metadata with. /// Provides a mechanism to create an instance of the class or struct when deserializing. /// Provides a mechanism to initialize metadata for properties and fields of the class or struct. - /// Provides a serialization implementation for instances of the class or struct which assumes options specified by . + /// Provides a serialization implementation for instances of the class or struct which assumes options specified by . /// Specifies how number properties and fields should be processed when serializing and deserializing. /// The type of the class or struct. /// Thrown when and are both null. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs index 208ac31..13bdbc8 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs @@ -24,7 +24,7 @@ namespace System.Text.Json.Serialization.Metadata /// /// A method that serializes an instance of using - /// values specified at design time. + /// values specified at design time. /// public Action? Serialize { get; private protected set; } } 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 5d4b469..0bb57ab 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 @@ -23,6 +23,55 @@ namespace System.Text.Json.SourceGeneration.Tests [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Metadata)] + internal partial class MetadataWithPerTypeAttributeContext : JsonSerializerContext, ITestContext + { + } + + public sealed class MetadataWithPerTypeAttributeContextTests : RealWorldContextTests + { + public MetadataWithPerTypeAttributeContextTests() : base(MetadataWithPerTypeAttributeContext.Default, (options) => new MetadataWithPerTypeAttributeContext(options)) { } + + [Fact] + public override void EnsureFastPathGeneratedAsExpected() + { + Assert.Null(MetadataWithPerTypeAttributeContext.Default.Location.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.RepeatedLocation.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.ActiveOrUpcomingEvent.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.CampaignSummaryViewModel.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.IndexViewModel.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.WeatherForecastWithPOCOs.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.EmptyPoco.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.HighLowTemps.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.MyType.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.MyType2.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.MyIntermediateType.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.HighLowTempsImmutable.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.MyNestedClass.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.MyNestedNestedClass.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.ObjectArray.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.String.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.ClassWithEnumAndNullable.Serialize); + } + } + + [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(Location))] + [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation")] + [JsonSerializable(typeof(ActiveOrUpcomingEvent))] + [JsonSerializable(typeof(CampaignSummaryViewModel))] + [JsonSerializable(typeof(IndexViewModel))] + [JsonSerializable(typeof(WeatherForecastWithPOCOs))] + [JsonSerializable(typeof(EmptyPoco))] + [JsonSerializable(typeof(HighLowTemps))] + [JsonSerializable(typeof(MyType))] + [JsonSerializable(typeof(MyType2))] + [JsonSerializable(typeof(MyIntermediateType))] + [JsonSerializable(typeof(HighLowTempsImmutable))] + [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass))] + [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass))] + [JsonSerializable(typeof(object[]))] + [JsonSerializable(typeof(string))] + [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable))] internal partial class MetadataContext : JsonSerializerContext, ITestContext { } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs index a92759a..f8317b5 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs @@ -14,15 +14,15 @@ namespace System.Text.Json.SourceGeneration.Tests [JsonSerializable(typeof(WeatherForecastWithPOCOs), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(EmptyPoco), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] - [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] - [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] + [JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Default)] + [JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] - [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] + [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] internal partial class MixedModeContext : JsonSerializerContext, ITestContext { } @@ -60,7 +60,7 @@ namespace System.Text.Json.SourceGeneration.Tests string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel), typeof(CampaignSummaryViewModel)); - IndexViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).IndexViewModel); + IndexViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).IndexViewModel); VerifyIndexViewModel(expected, obj); } @@ -72,7 +72,7 @@ namespace System.Text.Json.SourceGeneration.Tests string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel), typeof(CampaignSummaryViewModel)); - CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel); + CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).CampaignSummaryViewModel); VerifyCampaignSummaryViewModel(expected, obj); AssertFastPathLogicCorrect(json, obj, DefaultContext.CampaignSummaryViewModel); @@ -86,7 +86,7 @@ namespace System.Text.Json.SourceGeneration.Tests string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs), typeof(HighLowTemps)); - WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).WeatherForecastWithPOCOs); + WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).WeatherForecastWithPOCOs); VerifyWeatherForecastWithPOCOs(expected, obj); } @@ -98,7 +98,7 @@ namespace System.Text.Json.SourceGeneration.Tests string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco), typeof(EmptyPoco)); - EmptyPoco obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).EmptyPoco); + EmptyPoco obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).EmptyPoco); VerifyEmptyPoco(expected, obj); AssertFastPathLogicCorrect(json, obj, DefaultContext.EmptyPoco); @@ -112,7 +112,7 @@ namespace System.Text.Json.SourceGeneration.Tests string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation), typeof(RepeatedTypes.Location)); - RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).RepeatedLocation); + RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).RepeatedLocation); VerifyRepeatedLocation(expected, obj); AssertFastPathLogicCorrect(json, obj, DefaultContext.RepeatedLocation); @@ -122,11 +122,11 @@ namespace System.Text.Json.SourceGeneration.Tests public override void HandlesNestedTypes() { string json = @"{""MyInt"":5}"; - MyNestedClass obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedClass); + MyNestedClass obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).MyNestedClass); Assert.Equal(5, obj.MyInt); Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass)); - MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedNestedClass); + MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).MyNestedNestedClass); Assert.Equal(5, obj2.MyInt); Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass)); } @@ -138,12 +138,12 @@ namespace System.Text.Json.SourceGeneration.Tests CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray); - object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); + object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).ObjectArray); JsonElement indexAsJsonElement = (JsonElement)arr[0]; JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; - VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).IndexViewModel)); - VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel)); + VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), ((ITestContext)MetadataWithPerTypeAttributeContext.Default).IndexViewModel)); + VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), ((ITestContext)MetadataWithPerTypeAttributeContext.Default).CampaignSummaryViewModel)); } [Fact] @@ -156,7 +156,7 @@ namespace System.Text.Json.SourceGeneration.Tests Assert.Same(JsonNamingPolicy.CamelCase, ((JsonSerializerContext)context).Options.PropertyNamingPolicy); string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, context.ObjectArray); - object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); + object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).ObjectArray); JsonElement indexAsJsonElement = (JsonElement)arr[0]; JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs index 45ce3f3..02567c4 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs @@ -6,6 +6,28 @@ using Xunit; namespace System.Text.Json.SourceGeneration.Tests { + [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(Location))] + [JsonSerializable(typeof(RepeatedTypes.Location), TypeInfoPropertyName = "RepeatedLocation")] + [JsonSerializable(typeof(ActiveOrUpcomingEvent))] + [JsonSerializable(typeof(CampaignSummaryViewModel))] + [JsonSerializable(typeof(IndexViewModel))] + [JsonSerializable(typeof(WeatherForecastWithPOCOs))] + [JsonSerializable(typeof(EmptyPoco))] + [JsonSerializable(typeof(HighLowTemps))] + [JsonSerializable(typeof(MyType))] + [JsonSerializable(typeof(MyType2))] + [JsonSerializable(typeof(MyIntermediateType))] + [JsonSerializable(typeof(HighLowTempsImmutable))] + [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass))] + [JsonSerializable(typeof(RealWorldContextTests.MyNestedClass.MyNestedNestedClass))] + [JsonSerializable(typeof(object[]))] + [JsonSerializable(typeof(string))] + [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable))] + internal partial class SerializationContext : JsonSerializerContext, ITestContext + { + } + [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(RepeatedTypes.Location), GenerationMode = JsonSourceGenerationMode.Serialization, TypeInfoPropertyName = "RepeatedLocation")] [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Serialization)] @@ -23,11 +45,11 @@ namespace System.Text.Json.SourceGeneration.Tests [JsonSerializable(typeof(object[]), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)] - internal partial class SerializationContext : JsonSerializerContext, ITestContext + internal partial class SerializationWithPerTypeAttributeContext : JsonSerializerContext, ITestContext { } - [JsonSerializerOptions(NamingPolicy = JsonKnownNamingPolicy.BuiltInCamelCase)] + [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] [JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(RepeatedTypes.Location), GenerationMode = JsonSourceGenerationMode.Serialization, TypeInfoPropertyName = "RepeatedLocation")] [JsonSerializable(typeof(ActiveOrUpcomingEvent), GenerationMode = JsonSourceGenerationMode.Serialization)] @@ -49,9 +71,14 @@ namespace System.Text.Json.SourceGeneration.Tests { } - public sealed class SerializationContextTests : RealWorldContextTests + public class SerializationContextTests : RealWorldContextTests { - public SerializationContextTests() : base(SerializationContext.Default, (options) => new SerializationContext(options)) { } + public SerializationContextTests() : this(SerializationContext.Default, (options) => new SerializationContext(options)) { } + + internal SerializationContextTests(ITestContext defaultContext, Func contextCreator) + : base(defaultContext, contextCreator) + { + } [Fact] public override void EnsureFastPathGeneratedAsExpected() @@ -83,7 +110,7 @@ namespace System.Text.Json.SourceGeneration.Tests string json = JsonSerializer.Serialize(expected, DefaultContext.Location); JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.Location), typeof(Location)); - Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).Location); + Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).Location); VerifyLocation(expected, obj); AssertFastPathLogicCorrect(json, obj, DefaultContext.Location); @@ -97,7 +124,7 @@ namespace System.Text.Json.SourceGeneration.Tests string json = JsonSerializer.Serialize(expected, DefaultContext.IndexViewModel); JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.IndexViewModel), typeof(IndexViewModel)); - IndexViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).IndexViewModel); + IndexViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).IndexViewModel); VerifyIndexViewModel(expected, obj); AssertFastPathLogicCorrect(json, obj, DefaultContext.IndexViewModel); @@ -111,7 +138,7 @@ namespace System.Text.Json.SourceGeneration.Tests string json = JsonSerializer.Serialize(expected, DefaultContext.CampaignSummaryViewModel); JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.CampaignSummaryViewModel), typeof(CampaignSummaryViewModel)); - CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel); + CampaignSummaryViewModel obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).CampaignSummaryViewModel); VerifyCampaignSummaryViewModel(expected, obj); AssertFastPathLogicCorrect(json, obj, DefaultContext.CampaignSummaryViewModel); @@ -125,7 +152,7 @@ namespace System.Text.Json.SourceGeneration.Tests string json = JsonSerializer.Serialize(expected, DefaultContext.ActiveOrUpcomingEvent); JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.ActiveOrUpcomingEvent), typeof(ActiveOrUpcomingEvent)); - ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ActiveOrUpcomingEvent); + ActiveOrUpcomingEvent obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).ActiveOrUpcomingEvent); VerifyActiveOrUpcomingEvent(expected, obj); AssertFastPathLogicCorrect(json, obj, DefaultContext.ActiveOrUpcomingEvent); @@ -139,7 +166,7 @@ namespace System.Text.Json.SourceGeneration.Tests string json = JsonSerializer.Serialize(expected, DefaultContext.WeatherForecastWithPOCOs); JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.WeatherForecastWithPOCOs), typeof(WeatherForecastWithPOCOs)); - WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).WeatherForecastWithPOCOs); + WeatherForecastWithPOCOs obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).WeatherForecastWithPOCOs); VerifyWeatherForecastWithPOCOs(expected, obj); AssertFastPathLogicCorrect(json, obj, DefaultContext.WeatherForecastWithPOCOs); @@ -153,7 +180,7 @@ namespace System.Text.Json.SourceGeneration.Tests string json = JsonSerializer.Serialize(expected, DefaultContext.EmptyPoco); JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.EmptyPoco), typeof(EmptyPoco)); - EmptyPoco obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).EmptyPoco); + EmptyPoco obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).EmptyPoco); VerifyEmptyPoco(expected, obj); AssertFastPathLogicCorrect(json, obj, DefaultContext.EmptyPoco); @@ -167,7 +194,7 @@ namespace System.Text.Json.SourceGeneration.Tests string json = JsonSerializer.Serialize(expected, DefaultContext.RepeatedLocation); JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.RepeatedLocation), typeof(RepeatedTypes.Location)); - RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).RepeatedLocation); + RepeatedTypes.Location obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).RepeatedLocation); VerifyRepeatedLocation(expected, obj); AssertFastPathLogicCorrect(json, obj, DefaultContext.RepeatedLocation); @@ -178,12 +205,12 @@ namespace System.Text.Json.SourceGeneration.Tests { MyType myType = new() { Type = new() }; string json = JsonSerializer.Serialize(myType, DefaultContext.MyType); - myType = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyType); + myType = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).MyType); AssertFastPathLogicCorrect(json, myType, DefaultContext.MyType); MyType2 myType2 = new() { Type = new MyIntermediateType() { Type = myType } }; json = JsonSerializer.Serialize(myType2, DefaultContext.MyType2); - myType2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyType2); + myType2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).MyType2); AssertFastPathLogicCorrect(json, myType2, DefaultContext.MyType2); } @@ -194,12 +221,12 @@ namespace System.Text.Json.SourceGeneration.Tests CampaignSummaryViewModel campaignSummary = CreateCampaignSummaryViewModel(); string json = JsonSerializer.Serialize(new object[] { index, campaignSummary }, DefaultContext.ObjectArray); - object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); + object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).ObjectArray); JsonElement indexAsJsonElement = (JsonElement)arr[0]; JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; - VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).IndexViewModel)); - VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), ((ITestContext)MetadataContext.Default).CampaignSummaryViewModel)); + VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), ((ITestContext)MetadataWithPerTypeAttributeContext.Default).IndexViewModel)); + VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), ((ITestContext)MetadataWithPerTypeAttributeContext.Default).CampaignSummaryViewModel)); } [Fact] @@ -218,7 +245,7 @@ namespace System.Text.Json.SourceGeneration.Tests Assert.Contains("description", json); Assert.Contains("organizationName", json); - object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ObjectArray); + object[] arr = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).ObjectArray); JsonElement indexAsJsonElement = (JsonElement)arr[0]; JsonElement campaignSummeryAsJsonElement = (JsonElement)arr[1]; @@ -235,7 +262,7 @@ namespace System.Text.Json.SourceGeneration.Tests ITestContext context = new SerializationContext(options); string json = JsonSerializer.Serialize(new object[] { "Hello", "World" }, typeof(object[]), (JsonSerializerContext)context); - object[] arr = (object[])JsonSerializer.Deserialize(json, typeof(object[]), (JsonSerializerContext)((ITestContext)MetadataContext.Default)); + object[] arr = (object[])JsonSerializer.Deserialize(json, typeof(object[]), (JsonSerializerContext)((ITestContext)MetadataWithPerTypeAttributeContext.Default)); JsonElement hello = (JsonElement)arr[0]; JsonElement world = (JsonElement)arr[1]; @@ -247,11 +274,11 @@ namespace System.Text.Json.SourceGeneration.Tests public override void HandlesNestedTypes() { string json = @"{""MyInt"":5}"; - MyNestedClass obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedClass); + MyNestedClass obj = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).MyNestedClass); Assert.Equal(5, obj.MyInt); Assert.Equal(json, JsonSerializer.Serialize(obj, DefaultContext.MyNestedClass)); - MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).MyNestedNestedClass); + MyNestedClass.MyNestedNestedClass obj2 = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).MyNestedNestedClass); Assert.Equal(5, obj2.MyInt); Assert.Equal(json, JsonSerializer.Serialize(obj2, DefaultContext.MyNestedNestedClass)); } @@ -265,7 +292,7 @@ namespace System.Text.Json.SourceGeneration.Tests void RunTest(ClassWithEnumAndNullable expected) { string json = JsonSerializer.Serialize(expected, DefaultContext.ClassWithEnumAndNullable); - ClassWithEnumAndNullable actual = JsonSerializer.Deserialize(json, ((ITestContext)MetadataContext.Default).ClassWithEnumAndNullable); + ClassWithEnumAndNullable actual = JsonSerializer.Deserialize(json, ((ITestContext)MetadataWithPerTypeAttributeContext.Default).ClassWithEnumAndNullable); Assert.Equal(expected.Day, actual.Day); Assert.Equal(expected.NullableDay, actual.NullableDay); } @@ -281,4 +308,31 @@ namespace System.Text.Json.SourceGeneration.Tests JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsImmutable), typeof(HighLowTempsImmutable)); } } + + public sealed class SerializationWithPerTypeAttributeContextTests : SerializationContextTests + { + public SerializationWithPerTypeAttributeContextTests() : base(SerializationWithPerTypeAttributeContext.Default, (options) => new SerializationContext(options)) { } + + [Fact] + public override void EnsureFastPathGeneratedAsExpected() + { + Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.Location.Serialize); + Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.RepeatedLocation.Serialize); + Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.ActiveOrUpcomingEvent.Serialize); + Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.CampaignSummaryViewModel.Serialize); + Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.IndexViewModel.Serialize); + Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.WeatherForecastWithPOCOs.Serialize); + Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.WeatherForecastWithPOCOs.Serialize); + Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.HighLowTemps.Serialize); + Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.MyType.Serialize); + Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.MyType2.Serialize); + Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.MyIntermediateType.Serialize); + Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.HighLowTempsImmutable.Serialize); + Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.MyNestedClass.Serialize); + Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.MyNestedNestedClass.Serialize); + Assert.Null(SerializationWithPerTypeAttributeContext.Default.ObjectArray.Serialize); + Assert.Null(SerializationWithPerTypeAttributeContext.Default.String.Serialize); + Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.ClassWithEnumAndNullable.Serialize); + } + } }