Extend JsonSourceGenerationOptionsAttribute to have feature parity with JsonSerialize...
authorEirik Tsarpalis <eirik.tsarpalis@gmail.com>
Fri, 14 Jul 2023 16:37:45 +0000 (17:37 +0100)
committerGitHub <noreply@github.com>
Fri, 14 Jul 2023 16:37:45 +0000 (17:37 +0100)
18 files changed:
src/libraries/System.Text.Json/Common/JsonCommentHandling.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/JsonCommentHandling.cs with 100% similarity]
src/libraries/System.Text.Json/Common/JsonSerializerDefaults.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerDefaults.cs with 100% similarity]
src/libraries/System.Text.Json/Common/JsonSourceGenerationOptionsAttribute.cs
src/libraries/System.Text.Json/Common/JsonUnknownTypeHandling.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonUnknownTypeHandling.cs with 100% similarity]
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
src/libraries/System.Text.Json/gen/Model/ContextGenerationSpec.cs
src/libraries/System.Text.Json/gen/Model/PropertyGenerationSpec.cs
src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs [new file with mode: 0644]
src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets
src/libraries/System.Text.Json/ref/System.Text.Json.cs
src/libraries/System.Text.Json/src/System.Text.Json.csproj
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs

index 45f11a4..3d9e164 100644 (file)
@@ -16,11 +16,58 @@ namespace System.Text.Json.Serialization
     sealed class JsonSourceGenerationOptionsAttribute : JsonAttribute
     {
         /// <summary>
+        /// Constructs a new <see cref="JsonSourceGenerationOptionsAttribute"/> instance.
+        /// </summary>
+        public JsonSourceGenerationOptionsAttribute() { }
+
+        /// <summary>
+        /// Constructs a new <see cref="JsonSourceGenerationOptionsAttribute"/> instance with a predefined set of options determined by the specified <see cref="JsonSerializerDefaults"/>.
+        /// </summary>
+        /// <param name="defaults">The <see cref="JsonSerializerDefaults"/> to reason about.</param>
+        /// <exception cref="ArgumentOutOfRangeException">Invalid <paramref name="defaults"/> parameter.</exception>
+        public JsonSourceGenerationOptionsAttribute(JsonSerializerDefaults defaults)
+        {
+            // Constructor kept in sync with equivalent overload in JsonSerializerOptions
+
+            if (defaults is JsonSerializerDefaults.Web)
+            {
+                PropertyNameCaseInsensitive = true;
+                PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase;
+                NumberHandling = JsonNumberHandling.AllowReadingFromString;
+            }
+            else if (defaults is not JsonSerializerDefaults.General)
+            {
+                throw new ArgumentOutOfRangeException(nameof(defaults));
+            }
+        }
+
+        /// <summary>
+        /// Defines whether an extra comma at the end of a list of JSON values in an object or array
+        /// is allowed (and ignored) within the JSON payload being deserialized.
+        /// </summary>
+        public bool AllowTrailingCommas { get; set; }
+
+        /// <summary>
+        /// Specifies a list of custom converter types to be used.
+        /// </summary>
+        public Type[]? Converters { get; set; }
+
+        /// <summary>
+        /// The default buffer size in bytes used when creating temporary buffers.
+        /// </summary>
+        public int DefaultBufferSize { get; set; }
+
+        /// <summary>
         /// Specifies the default ignore condition.
         /// </summary>
         public JsonIgnoreCondition DefaultIgnoreCondition { get; set; }
 
         /// <summary>
+        /// Specifies the policy used to convert a dictionary key to another format, such as camel-casing.
+        /// </summary>
+        public JsonKnownNamingPolicy DictionaryKeyPolicy { get; set; }
+
+        /// <summary>
         /// Specifies whether to ignore read-only fields.
         /// </summary>
         public bool IgnoreReadOnlyFields { get; set; }
@@ -36,11 +83,47 @@ namespace System.Text.Json.Serialization
         public bool IncludeFields { get; set; }
 
         /// <summary>
+        /// Gets or sets the maximum depth allowed when serializing or deserializing JSON, with the default (i.e. 0) indicating a max depth of 64.
+        /// </summary>
+        public int MaxDepth { get; set; }
+
+        /// <summary>
+        /// Specifies how number types should be handled when serializing or deserializing.
+        /// </summary>
+        public JsonNumberHandling NumberHandling { get; set; }
+
+        /// <summary>
+        /// Specifies preferred object creation handling for properties when deserializing JSON.
+        /// </summary>
+        public JsonObjectCreationHandling PreferredObjectCreationHandling { get; set; }
+
+        /// <summary>
+        /// Determines whether a property name uses a case-insensitive comparison during deserialization.
+        /// </summary>
+        public bool PropertyNameCaseInsensitive { get; set; }
+
+        /// <summary>
         /// Specifies a built-in naming polices to convert JSON property names with.
         /// </summary>
         public JsonKnownNamingPolicy PropertyNamingPolicy { get; set; }
 
         /// <summary>
+        /// Defines how JSON comments are handled during deserialization.
+        /// </summary>
+        public JsonCommentHandling ReadCommentHandling { get; set; }
+
+        /// <summary>
+        /// Defines how deserializing a type declared as an <see cref="object"/> is handled during deserialization.
+        /// </summary>
+        public JsonUnknownTypeHandling UnknownTypeHandling { get; set; }
+
+        /// <summary>
+        /// Determines how <see cref="JsonSerializer"/> handles JSON properties that
+        /// cannot be mapped to a specific .NET member when deserializing object types.
+        /// </summary>
+        public JsonUnmappedMemberHandling UnmappedMemberHandling { get; set; }
+
+        /// <summary>
         /// Specifies whether JSON output should be pretty-printed.
         /// </summary>
         public bool WriteIndented { get; set; }
index a6b61db..eca1f27 100644 (file)
@@ -53,13 +53,16 @@ namespace System.Text.Json.SourceGeneration
             private const string JsonSerializerOptionsTypeRef = "global::System.Text.Json.JsonSerializerOptions";
             private const string JsonSerializerContextTypeRef = "global::System.Text.Json.Serialization.JsonSerializerContext";
             private const string Utf8JsonWriterTypeRef = "global::System.Text.Json.Utf8JsonWriter";
+            private const string JsonCommentHandlingTypeRef = "global::System.Text.Json.JsonCommentHandling";
             private const string JsonConverterTypeRef = "global::System.Text.Json.Serialization.JsonConverter";
             private const string JsonConverterFactoryTypeRef = "global::System.Text.Json.Serialization.JsonConverterFactory";
             private const string JsonCollectionInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues";
             private const string JsonIgnoreConditionTypeRef = "global::System.Text.Json.Serialization.JsonIgnoreCondition";
+            private const string JsonSerializerDefaultsTypeRef = "global::System.Text.Json.JsonSerializerDefaults";
             private const string JsonNumberHandlingTypeRef = "global::System.Text.Json.Serialization.JsonNumberHandling";
             private const string JsonObjectCreationHandlingTypeRef = "global::System.Text.Json.Serialization.JsonObjectCreationHandling";
             private const string JsonUnmappedMemberHandlingTypeRef = "global::System.Text.Json.Serialization.JsonUnmappedMemberHandling";
+            private const string JsonUnknownTypeHandlingTypeRef = "global::System.Text.Json.Serialization.JsonUnknownTypeHandling";
             private const string JsonMetadataServicesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonMetadataServices";
             private const string JsonObjectInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues";
             private const string JsonParameterInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues";
@@ -380,7 +383,7 @@ namespace System.Text.Json.SourceGeneration
                     };
 
                     {{JsonTypeInfoLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.{{createCollectionMethodExpr}};
-                    {{JsonTypeInfoLocalVariableName}}.{{NumberHandlingPropName}} = {{GetNumberHandlingAsStr(typeGenerationSpec.NumberHandling)}};
+                    {{JsonTypeInfoLocalVariableName}}.{{NumberHandlingPropName}} = {{FormatNumberHandling(typeGenerationSpec.NumberHandling)}};
                     """);
 
                 GenerateTypeInfoFactoryFooter(writer);
@@ -533,7 +536,7 @@ namespace System.Text.Json.SourceGeneration
                     };
 
                     {{JsonTypeInfoLocalVariableName}} = {{JsonMetadataServicesTypeRef}}.CreateObjectInfo<{{typeMetadata.TypeRef.FullyQualifiedName}}>({{OptionsLocalVariableName}}, {{ObjectInfoVarName}});
-                    {{JsonTypeInfoLocalVariableName}}.{{NumberHandlingPropName}} = {{GetNumberHandlingAsStr(typeMetadata.NumberHandling)}};
+                    {{JsonTypeInfoLocalVariableName}}.{{NumberHandlingPropName}} = {{FormatNumberHandling(typeMetadata.NumberHandling)}};
                     """);
 
                 if (typeMetadata is { UnmappedMemberHandling: not null } or { PreferredPropertyObjectCreationHandling: not null })
@@ -542,12 +545,12 @@ namespace System.Text.Json.SourceGeneration
 
                     if (typeMetadata.UnmappedMemberHandling != null)
                     {
-                        writer.WriteLine($"{JsonTypeInfoLocalVariableName}.{UnmappedMemberHandlingPropName} = {GetUnmappedMemberHandlingAsStr(typeMetadata.UnmappedMemberHandling.Value)};");
+                        writer.WriteLine($"{JsonTypeInfoLocalVariableName}.{UnmappedMemberHandlingPropName} = {FormatUnmappedMemberHandling(typeMetadata.UnmappedMemberHandling.Value)};");
                     }
 
                     if (typeMetadata.PreferredPropertyObjectCreationHandling != null)
                     {
-                        writer.WriteLine($"{JsonTypeInfoLocalVariableName}.{PreferredPropertyObjectCreationHandlingPropName} = {GetObjectCreationHandlingAsStr(typeMetadata.PreferredPropertyObjectCreationHandling.Value)};");
+                        writer.WriteLine($"{JsonTypeInfoLocalVariableName}.{PreferredPropertyObjectCreationHandlingPropName} = {FormatObjectCreationHandling(typeMetadata.PreferredPropertyObjectCreationHandling.Value)};");
                     }
                 }
 
@@ -647,7 +650,7 @@ namespace System.Text.Json.SourceGeneration
                             IgnoreCondition = {{ignoreConditionNamedArg}},
                             HasJsonInclude = {{FormatBool(property.HasJsonInclude)}},
                             IsExtensionData = {{FormatBool(property.IsExtensionData)}},
-                            NumberHandling = {{GetNumberHandlingAsStr(property.NumberHandling)}},
+                            NumberHandling = {{FormatNumberHandling(property.NumberHandling)}},
                             PropertyName = {{FormatStringLiteral(property.MemberName)}},
                             JsonPropertyName = {{FormatStringLiteral(property.JsonPropertyName)}}
                         };
@@ -663,7 +666,7 @@ namespace System.Text.Json.SourceGeneration
 
                     if (property.ObjectCreationHandling != null)
                     {
-                        writer.WriteLine($"properties[{i}].ObjectCreationHandling = {GetObjectCreationHandlingAsStr(property.ObjectCreationHandling.Value)};");
+                        writer.WriteLine($"properties[{i}].ObjectCreationHandling = {FormatObjectCreationHandling(property.ObjectCreationHandling.Value)};");
                     }
 
                     writer.WriteLine();
@@ -769,12 +772,12 @@ namespace System.Text.Json.SourceGeneration
                         continue;
                     }
 
-                    string runtimePropName = propertyGenSpec.RuntimePropertyName;
-                    string propNameVarName = propertyGenSpec.PropertyNameVarName;
+                    string effectiveJsonPropertyName = propertyGenSpec.EffectiveJsonPropertyName;
+                    string propertyNameFieldName = propertyGenSpec.PropertyNameFieldName;
 
                     // Add the property names to the context-wide cache; we'll generate the source to initialize them at the end of generation.
-                    Debug.Assert(!_propertyNames.TryGetValue(runtimePropName, out string? existingName) || existingName == propNameVarName);
-                    _propertyNames.TryAdd(runtimePropName, propNameVarName);
+                    Debug.Assert(!_propertyNames.TryGetValue(effectiveJsonPropertyName, out string? existingName) || existingName == propertyNameFieldName);
+                    _propertyNames.TryAdd(effectiveJsonPropertyName, propertyNameFieldName);
 
                     DefaultCheckType defaultCheckType = GetDefaultCheckType(contextSpec, propertyGenSpec);
 
@@ -812,7 +815,7 @@ namespace System.Text.Json.SourceGeneration
                             break;
                     }
 
-                    GenerateSerializePropertyStatement(writer, propertyTypeSpec, propNameVarName, propValueExpr);
+                    GenerateSerializePropertyStatement(writer, propertyTypeSpec, propertyNameFieldName, propValueExpr);
 
                     if (defaultCheckType != DefaultCheckType.None)
                     {
@@ -979,7 +982,7 @@ namespace System.Text.Json.SourceGeneration
 
             private static DefaultCheckType GetDefaultCheckType(ContextGenerationSpec contextSpec, PropertyGenerationSpec propertySpec)
             {
-                return (propertySpec.DefaultIgnoreCondition ?? contextSpec.DefaultIgnoreCondition) switch
+                return (propertySpec.DefaultIgnoreCondition ?? contextSpec.GeneratedOptionsSpec?.DefaultIgnoreCondition) switch
                 {
                     JsonIgnoreCondition.WhenWritingNull => propertySpec.PropertyType.CanBeNull ? DefaultCheckType.Null : DefaultCheckType.None,
                     JsonIgnoreCondition.WhenWritingDefault => propertySpec.PropertyType.CanBeNull ? DefaultCheckType.Null : DefaultCheckType.Default,
@@ -1040,7 +1043,7 @@ namespace System.Text.Json.SourceGeneration
 
                 SourceWriter writer = CreateSourceWriterWithContextHeader(contextSpec, isPrimaryContextSourceFile: true);
 
-                GetLogicForDefaultSerializerOptionsInit(contextSpec, writer);
+                GetLogicForDefaultSerializerOptionsInit(contextSpec.GeneratedOptionsSpec, writer);
 
                 writer.WriteLine();
 
@@ -1073,33 +1076,110 @@ namespace System.Text.Json.SourceGeneration
                 return CompleteSourceFileAndReturnText(writer);
             }
 
-            private static void GetLogicForDefaultSerializerOptionsInit(ContextGenerationSpec contextSpec, SourceWriter writer)
+            private static void GetLogicForDefaultSerializerOptionsInit(SourceGenerationOptionsSpec? optionsSpec, SourceWriter writer)
             {
-                string? namingPolicyName = contextSpec.PropertyNamingPolicy switch
+                const string DefaultOptionsFieldDecl = $"private readonly static {JsonSerializerOptionsTypeRef} {DefaultOptionsStaticVarName}";
+
+                if (optionsSpec is null)
                 {
-                    JsonKnownNamingPolicy.CamelCase => nameof(JsonNamingPolicy.CamelCase),
-                    JsonKnownNamingPolicy.SnakeCaseLower => nameof(JsonNamingPolicy.SnakeCaseLower),
-                    JsonKnownNamingPolicy.SnakeCaseUpper => nameof(JsonNamingPolicy.SnakeCaseUpper),
-                    JsonKnownNamingPolicy.KebabCaseLower => nameof(JsonNamingPolicy.KebabCaseLower),
-                    JsonKnownNamingPolicy.KebabCaseUpper => nameof(JsonNamingPolicy.KebabCaseUpper),
-                    _ => null,
-                };
+                    writer.WriteLine($"{DefaultOptionsFieldDecl} = new();");
+                    return;
+                }
 
-                string namingPolicy = namingPolicyName != null
-                    ? $"{JsonNamingPolicyTypeRef}.{namingPolicyName}"
-                    : "null";
+                if (optionsSpec.Defaults is JsonSerializerDefaults defaults)
+                {
+                    writer.WriteLine($"{DefaultOptionsFieldDecl} = new({FormatJsonSerializerDefaults(defaults)})");
+                }
+                else
+                {
+                    writer.WriteLine($"{DefaultOptionsFieldDecl} = new()");
+                }
 
-                writer.WriteLine($$"""
-                    private readonly static {{JsonSerializerOptionsTypeRef}} {{DefaultOptionsStaticVarName}} = new()
+                writer.WriteLine('{');
+                writer.Indentation++;
+
+                if (optionsSpec.AllowTrailingCommas is bool allowTrailingCommas)
+                    writer.WriteLine($"AllowTrailingCommas = {FormatBool(allowTrailingCommas)},");
+
+                if (optionsSpec.Converters is { Count: > 0 } converters)
+                {
+                    writer.WriteLine("Converters =");
+                    writer.WriteLine('{');
+                    writer.Indentation++;
+
+                    foreach (TypeRef converter in converters)
                     {
-                        DefaultIgnoreCondition = {{JsonIgnoreConditionTypeRef}}.{{contextSpec.DefaultIgnoreCondition}},
-                        IgnoreReadOnlyFields = {{FormatBool(contextSpec.IgnoreReadOnlyFields)}},
-                        IgnoreReadOnlyProperties = {{FormatBool(contextSpec.IgnoreReadOnlyProperties)}},
-                        IncludeFields = {{FormatBool(contextSpec.IncludeFields)}},
-                        WriteIndented = {{FormatBool(contextSpec.WriteIndented)}},
-                        PropertyNamingPolicy = {{namingPolicy}}
+                        writer.WriteLine($"new {converter.FullyQualifiedName}(),");
+                    }
+
+                    writer.Indentation--;
+                    writer.WriteLine("},");
+                }
+
+                if (optionsSpec.DefaultBufferSize is int defaultBufferSize)
+                    writer.WriteLine($"DefaultBufferSize = {defaultBufferSize},");
+
+                if (optionsSpec.DefaultIgnoreCondition is JsonIgnoreCondition defaultIgnoreCondition)
+                    writer.WriteLine($"DefaultIgnoreCondition = {FormatIgnoreCondition(defaultIgnoreCondition)},");
+
+                if (optionsSpec.DictionaryKeyPolicy is JsonKnownNamingPolicy dictionaryKeyPolicy)
+                    writer.WriteLine($"DictionaryKeyPolicy = {FormatNamingPolicy(dictionaryKeyPolicy)},");
+
+                if (optionsSpec.IgnoreReadOnlyFields is bool ignoreReadOnlyFields)
+                    writer.WriteLine($"IgnoreReadOnlyFields = {FormatBool(ignoreReadOnlyFields)},");
+
+                if (optionsSpec.IgnoreReadOnlyProperties is bool ignoreReadOnlyProperties)
+                    writer.WriteLine($"IgnoreReadOnlyProperties = {FormatBool(ignoreReadOnlyProperties)},");
+
+                if (optionsSpec.IncludeFields is bool includeFields)
+                    writer.WriteLine($"IncludeFields = {FormatBool(includeFields)},");
+
+                if (optionsSpec.MaxDepth is int maxDepth)
+                    writer.WriteLine($"MaxDepth = {maxDepth},");
+
+                if (optionsSpec.NumberHandling is JsonNumberHandling numberHandling)
+                    writer.WriteLine($"NumberHandling = {FormatNumberHandling(numberHandling)},");
+
+                if (optionsSpec.PreferredObjectCreationHandling is JsonObjectCreationHandling preferredObjectCreationHandling)
+                    writer.WriteLine($"PreferredObjectCreationHandling = {FormatObjectCreationHandling(preferredObjectCreationHandling)},");
+
+                if (optionsSpec.PropertyNameCaseInsensitive is bool propertyNameCaseInsensitive)
+                    writer.WriteLine($"PropertyNameCaseInsensitive = {FormatBool(propertyNameCaseInsensitive)},");
+
+                if (optionsSpec.PropertyNamingPolicy is JsonKnownNamingPolicy propertyNamingPolicy)
+                    writer.WriteLine($"PropertyNamingPolicy = {FormatNamingPolicy(propertyNamingPolicy)},");
+
+                if (optionsSpec.ReadCommentHandling is JsonCommentHandling readCommentHandling)
+                    writer.WriteLine($"ReadCommentHandling = {FormatCommentHandling(readCommentHandling)},");
+
+                if (optionsSpec.UnknownTypeHandling is JsonUnknownTypeHandling unknownTypeHandling)
+                    writer.WriteLine($"UnknownTypeHandling = {FormatUnknownTypeHandling(unknownTypeHandling)},");
+
+                if (optionsSpec.UnmappedMemberHandling is JsonUnmappedMemberHandling unmappedMemberHandling)
+                    writer.WriteLine($"UnmappedMemberHandling = {FormatUnmappedMemberHandling(unmappedMemberHandling)},");
+
+                if (optionsSpec.WriteIndented is bool writeIndented)
+                    writer.WriteLine($"WriteIndented = {FormatBool(writeIndented)},");
+
+                writer.Indentation--;
+                writer.WriteLine("};");
+
+                static string FormatNamingPolicy(JsonKnownNamingPolicy knownNamingPolicy)
+                {
+                    string? policyName = knownNamingPolicy switch
+                    {
+                        JsonKnownNamingPolicy.CamelCase => nameof(JsonNamingPolicy.CamelCase),
+                        JsonKnownNamingPolicy.SnakeCaseLower => nameof(JsonNamingPolicy.SnakeCaseLower),
+                        JsonKnownNamingPolicy.SnakeCaseUpper => nameof(JsonNamingPolicy.SnakeCaseUpper),
+                        JsonKnownNamingPolicy.KebabCaseLower => nameof(JsonNamingPolicy.KebabCaseLower),
+                        JsonKnownNamingPolicy.KebabCaseUpper => nameof(JsonNamingPolicy.KebabCaseUpper),
+                        _ => null,
                     };
-                    """);
+
+                    return policyName != null
+                    ? $"{JsonNamingPolicyTypeRef}.{policyName}"
+                    : "null";
+                }
             }
 
             private static void GenerateConverterHelpers(SourceWriter writer, bool emitGetConverterForNullablePropertyMethod)
@@ -1230,17 +1310,29 @@ namespace System.Text.Json.SourceGeneration
                 return CompleteSourceFileAndReturnText(writer);
             }
 
-            private static string GetNumberHandlingAsStr(JsonNumberHandling? numberHandling)
+            private static string FormatNumberHandling(JsonNumberHandling? numberHandling)
                 => numberHandling.HasValue
                 ? SourceGeneratorHelpers.FormatEnumLiteral(JsonNumberHandlingTypeRef, numberHandling.Value)
                 : "null";
 
-            private static string GetObjectCreationHandlingAsStr(JsonObjectCreationHandling creationHandling)
+            private static string FormatObjectCreationHandling(JsonObjectCreationHandling creationHandling)
                 => SourceGeneratorHelpers.FormatEnumLiteral(JsonObjectCreationHandlingTypeRef, creationHandling);
 
-            private static string GetUnmappedMemberHandlingAsStr(JsonUnmappedMemberHandling unmappedMemberHandling)
+            private static string FormatUnmappedMemberHandling(JsonUnmappedMemberHandling unmappedMemberHandling)
                 => SourceGeneratorHelpers.FormatEnumLiteral(JsonUnmappedMemberHandlingTypeRef, unmappedMemberHandling);
 
+            private static string FormatCommentHandling(JsonCommentHandling commentHandling)
+                => SourceGeneratorHelpers.FormatEnumLiteral(JsonCommentHandlingTypeRef, commentHandling);
+
+            private static string FormatUnknownTypeHandling(JsonUnknownTypeHandling commentHandling)
+                => SourceGeneratorHelpers.FormatEnumLiteral(JsonUnknownTypeHandlingTypeRef, commentHandling);
+
+            private static string FormatIgnoreCondition(JsonIgnoreCondition ignoreCondition)
+                => SourceGeneratorHelpers.FormatEnumLiteral(JsonIgnoreConditionTypeRef, ignoreCondition);
+
+            private static string FormatJsonSerializerDefaults(JsonSerializerDefaults defaults)
+                => SourceGeneratorHelpers.FormatEnumLiteral(JsonSerializerDefaultsTypeRef, defaults);
+
             private static string GetCreateValueInfoMethodRef(string typeCompilableName) => $"{CreateValueInfoMethodName}<{typeCompilableName}>";
 
             private static string FormatBool(bool value) => value ? "true" : "false";
@@ -1403,19 +1495,19 @@ namespace System.Text.Json.SourceGeneration
                 // Algorithm should be kept in sync with the reflection equivalent in JsonTypeInfo.cs
                 string memberName = propertySpec.MemberName;
 
-                if (state.AddedProperties.TryAdd(propertySpec.RuntimePropertyName, (propertySpec, state.Properties.Count)))
+                if (state.AddedProperties.TryAdd(propertySpec.EffectiveJsonPropertyName, (propertySpec, state.Properties.Count)))
                 {
                     state.Properties.Add(propertySpec);
                 }
                 else
                 {
                     // The JsonPropertyNameAttribute or naming policy resulted in a collision.
-                    (PropertyGenerationSpec other, int index) = state.AddedProperties[propertySpec.RuntimePropertyName];
+                    (PropertyGenerationSpec other, int index) = state.AddedProperties[propertySpec.EffectiveJsonPropertyName];
 
                     if (other.DefaultIgnoreCondition == JsonIgnoreCondition.Always)
                     {
                         // Overwrite previously cached property since it has [JsonIgnore].
-                        state.AddedProperties[propertySpec.RuntimePropertyName] = (propertySpec, index);
+                        state.AddedProperties[propertySpec.EffectiveJsonPropertyName] = (propertySpec, index);
                         state.Properties[index] = propertySpec;
                     }
                     else
index 423d340..b8bc986 100644 (file)
@@ -94,7 +94,7 @@ namespace System.Text.Json.SourceGeneration
                 if (!TryParseJsonSerializerContextAttributes(
                     contextTypeSymbol,
                     out List<TypeToGenerate>? rootSerializableTypes,
-                    out JsonSourceGenerationOptionsAttribute? options))
+                    out SourceGenerationOptionsSpec? options))
                 {
                     // Context does not specify any source gen attributes.
                     return null;
@@ -122,8 +122,6 @@ namespace System.Text.Json.SourceGeneration
                     return null;
                 }
 
-                options ??= new JsonSourceGenerationOptionsAttribute();
-
                 // Enqueue attribute data for spec generation
                 foreach (TypeToGenerate rootSerializableType in rootSerializableTypes)
                 {
@@ -150,12 +148,7 @@ namespace System.Text.Json.SourceGeneration
                     GeneratedTypes = _generatedTypes.Values.OrderBy(t => t.TypeRef.FullyQualifiedName).ToImmutableEquatableArray(),
                     Namespace = contextTypeSymbol.ContainingNamespace is { IsGlobalNamespace: false } ns ? ns.ToDisplayString() : null,
                     ContextClassDeclarations = classDeclarationList.ToImmutableEquatableArray(),
-                    DefaultIgnoreCondition = options.DefaultIgnoreCondition,
-                    IgnoreReadOnlyFields = options.IgnoreReadOnlyFields,
-                    IgnoreReadOnlyProperties = options.IgnoreReadOnlyProperties,
-                    IncludeFields = options.IncludeFields,
-                    PropertyNamingPolicy = options.PropertyNamingPolicy,
-                    WriteIndented = options.WriteIndented,
+                    GeneratedOptionsSpec = options,
                 };
 
                 // Clear the caches of generated metadata between the processing of context classes.
@@ -224,9 +217,9 @@ namespace System.Text.Json.SourceGeneration
             }
 
             private bool TryParseJsonSerializerContextAttributes(
-                ITypeSymbol contextClassSymbol,
+                INamedTypeSymbol contextClassSymbol,
                 out List<TypeToGenerate>? rootSerializableTypes,
-                out JsonSourceGenerationOptionsAttribute? options)
+                out SourceGenerationOptionsSpec? options)
             {
                 Debug.Assert(_knownSymbols.JsonSerializableAttributeType != null);
                 Debug.Assert(_knownSymbols.JsonSourceGenerationOptionsAttributeType != null);
@@ -250,47 +243,125 @@ namespace System.Text.Json.SourceGeneration
                     }
                     else if (SymbolEqualityComparer.Default.Equals(attributeClass, _knownSymbols.JsonSourceGenerationOptionsAttributeType))
                     {
-                        options = ParseJsonSourceGenerationOptionsAttribute(attributeData);
+                        options = ParseJsonSourceGenerationOptionsAttribute(contextClassSymbol, attributeData);
                     }
                 }
 
                 return rootSerializableTypes != null || options != null;
             }
 
-            private static JsonSourceGenerationOptionsAttribute ParseJsonSourceGenerationOptionsAttribute(AttributeData attributeData)
+            private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(INamedTypeSymbol contextType, AttributeData attributeData)
             {
-                JsonSourceGenerationOptionsAttribute options = new();
+                JsonSourceGenerationMode? generationMode = null;
+                List<TypeRef>? converters = null;
+                JsonSerializerDefaults? defaults = null;
+                bool? allowTrailingCommas = null;
+                int? defaultBufferSize = null;
+                JsonIgnoreCondition? defaultIgnoreCondition = null;
+                JsonKnownNamingPolicy? dictionaryKeyPolicy = null;
+                bool? ignoreReadOnlyFields = null;
+                bool? ignoreReadOnlyProperties = null;
+                bool? includeFields = null;
+                int? maxDepth = null;
+                JsonNumberHandling? numberHandling = null;
+                JsonObjectCreationHandling? preferredObjectCreationHandling = null;
+                bool? propertyNameCaseInsensitive = null;
+                JsonKnownNamingPolicy? propertyNamingPolicy = null;
+                JsonCommentHandling? readCommentHandling = null;
+                JsonUnknownTypeHandling? unknownTypeHandling = null;
+                JsonUnmappedMemberHandling? unmappedMemberHandling = null;
+                bool? writeIndented = null;
+
+                if (attributeData.ConstructorArguments.Length > 0)
+                {
+                    Debug.Assert(attributeData.ConstructorArguments.Length == 1 & attributeData.ConstructorArguments[0].Type?.Name is nameof(JsonSerializerDefaults));
+                    defaults = (JsonSerializerDefaults)attributeData.ConstructorArguments[0].Value!;
+                }
 
                 foreach (KeyValuePair<string, TypedConstant> namedArg in attributeData.NamedArguments)
                 {
                     switch (namedArg.Key)
                     {
+                        case nameof(JsonSourceGenerationOptionsAttribute.AllowTrailingCommas):
+                            allowTrailingCommas = (bool)namedArg.Value.Value!;
+                            break;
+
+                        case nameof(JsonSourceGenerationOptionsAttribute.Converters):
+                            converters = new List<TypeRef>();
+                            foreach (TypedConstant element in namedArg.Value.Values)
+                            {
+                                var converterType = (ITypeSymbol?)element.Value;
+                                TypeRef? typeRef = GetConverterTypeFromAttribute(contextType, converterType, contextType, attributeData);
+                                if (typeRef != null)
+                                {
+                                    converters.Add(typeRef);
+                                }
+                            }
+
+                            break;
+
+                        case nameof(JsonSourceGenerationOptionsAttribute.DefaultBufferSize):
+                            defaultBufferSize = (int)namedArg.Value.Value!;
+                            break;
+
                         case nameof(JsonSourceGenerationOptionsAttribute.DefaultIgnoreCondition):
-                            options.DefaultIgnoreCondition = (JsonIgnoreCondition)namedArg.Value.Value!;
+                            defaultIgnoreCondition = (JsonIgnoreCondition)namedArg.Value.Value!;
+                            break;
+
+                        case nameof(JsonSourceGenerationOptionsAttribute.DictionaryKeyPolicy):
+                            dictionaryKeyPolicy = (JsonKnownNamingPolicy)namedArg.Value.Value!;
                             break;
 
                         case nameof(JsonSourceGenerationOptionsAttribute.IgnoreReadOnlyFields):
-                            options.IgnoreReadOnlyFields = (bool)namedArg.Value.Value!;
+                            ignoreReadOnlyFields = (bool)namedArg.Value.Value!;
                             break;
 
                         case nameof(JsonSourceGenerationOptionsAttribute.IgnoreReadOnlyProperties):
-                            options.IgnoreReadOnlyProperties = (bool)namedArg.Value.Value!;
+                            ignoreReadOnlyProperties = (bool)namedArg.Value.Value!;
                             break;
 
                         case nameof(JsonSourceGenerationOptionsAttribute.IncludeFields):
-                            options.IncludeFields = (bool)namedArg.Value.Value!;
+                            includeFields = (bool)namedArg.Value.Value!;
+                            break;
+
+                        case nameof(JsonSourceGenerationOptionsAttribute.MaxDepth):
+                            maxDepth = (int)namedArg.Value.Value!;
+                            break;
+
+                        case nameof(JsonSourceGenerationOptionsAttribute.NumberHandling):
+                            numberHandling = (JsonNumberHandling)namedArg.Value.Value!;
+                            break;
+
+                        case nameof(JsonSourceGenerationOptionsAttribute.PreferredObjectCreationHandling):
+                            preferredObjectCreationHandling = (JsonObjectCreationHandling)namedArg.Value.Value!;
+                            break;
+
+                        case nameof(JsonSourceGenerationOptionsAttribute.PropertyNameCaseInsensitive):
+                            propertyNameCaseInsensitive = (bool)namedArg.Value.Value!;
                             break;
 
                         case nameof(JsonSourceGenerationOptionsAttribute.PropertyNamingPolicy):
-                            options.PropertyNamingPolicy = (JsonKnownNamingPolicy)namedArg.Value.Value!;
+                            propertyNamingPolicy = (JsonKnownNamingPolicy)namedArg.Value.Value!;
+                            break;
+
+                        case nameof(JsonSourceGenerationOptionsAttribute.ReadCommentHandling):
+                            readCommentHandling = (JsonCommentHandling)namedArg.Value.Value!;
+                            break;
+
+                        case nameof(JsonSourceGenerationOptionsAttribute.UnknownTypeHandling):
+                            unknownTypeHandling = (JsonUnknownTypeHandling)namedArg.Value.Value!;
+                            break;
+
+                        case nameof(JsonSourceGenerationOptionsAttribute.UnmappedMemberHandling):
+                            unmappedMemberHandling = (JsonUnmappedMemberHandling)namedArg.Value.Value!;
                             break;
 
                         case nameof(JsonSourceGenerationOptionsAttribute.WriteIndented):
-                            options.WriteIndented = (bool)namedArg.Value.Value!;
+                            writeIndented = (bool)namedArg.Value.Value!;
                             break;
 
                         case nameof(JsonSourceGenerationOptionsAttribute.GenerationMode):
-                            options.GenerationMode = (JsonSourceGenerationMode)namedArg.Value.Value!;
+                            generationMode = (JsonSourceGenerationMode)namedArg.Value.Value!;
                             break;
 
                         default:
@@ -298,7 +369,28 @@ namespace System.Text.Json.SourceGeneration
                     }
                 }
 
-                return options;
+                return new SourceGenerationOptionsSpec
+                {
+                    GenerationMode = generationMode,
+                    Defaults = defaults,
+                    AllowTrailingCommas = allowTrailingCommas,
+                    DefaultBufferSize = defaultBufferSize,
+                    Converters = converters?.ToImmutableEquatableArray(),
+                    DefaultIgnoreCondition = defaultIgnoreCondition,
+                    DictionaryKeyPolicy = dictionaryKeyPolicy,
+                    IgnoreReadOnlyFields = ignoreReadOnlyFields,
+                    IgnoreReadOnlyProperties = ignoreReadOnlyProperties,
+                    IncludeFields = includeFields,
+                    MaxDepth = maxDepth,
+                    NumberHandling = numberHandling,
+                    PreferredObjectCreationHandling = preferredObjectCreationHandling,
+                    PropertyNameCaseInsensitive = propertyNameCaseInsensitive,
+                    PropertyNamingPolicy = propertyNamingPolicy,
+                    ReadCommentHandling = readCommentHandling,
+                    UnknownTypeHandling = unknownTypeHandling,
+                    UnmappedMemberHandling = unmappedMemberHandling,
+                    WriteIndented = writeIndented,
+                };
             }
 
             private static TypeToGenerate? ParseJsonSerializableAttribute(AttributeData attributeData)
@@ -342,7 +434,7 @@ namespace System.Text.Json.SourceGeneration
                 };
             }
 
-            private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGenerate, INamedTypeSymbol contextType, Location contextLocation, JsonSourceGenerationOptionsAttribute options)
+            private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGenerate, INamedTypeSymbol contextType, Location contextLocation, SourceGenerationOptionsSpec? options)
             {
                 Debug.Assert(IsSymbolAccessibleWithin(typeToGenerate.Type, within: contextType), "should not generate metadata for inaccessible types.");
 
@@ -481,7 +573,7 @@ namespace System.Text.Json.SourceGeneration
                 {
                     TypeRef = typeRef,
                     TypeInfoPropertyName = typeInfoPropertyName,
-                    GenerationMode = typeToGenerate.Mode ?? options.GenerationMode,
+                    GenerationMode = typeToGenerate.Mode ?? options?.GenerationMode ?? JsonSourceGenerationMode.Default,
                     ClassType = classType,
                     PrimitiveTypeKind = primitiveTypeKind,
                     IsPolymorphic = isPolymorphic,
@@ -546,7 +638,7 @@ namespace System.Text.Json.SourceGeneration
                     }
                     else if (!foundJsonConverterAttribute && _knownSymbols.JsonConverterAttributeType.IsAssignableFrom(attributeType))
                     {
-                        customConverterType = GetConverterTypeFromAttribute(contextType, typeToGenerate.Type, attributeData);
+                        customConverterType = GetConverterTypeFromJsonConverterAttribute(contextType, typeToGenerate.Type, attributeData);
                         foundJsonConverterAttribute = true;
                     }
 
@@ -735,7 +827,7 @@ namespace System.Text.Json.SourceGeneration
                 INamedTypeSymbol contextType,
                 in TypeToGenerate typeToGenerate,
                 Location typeLocation,
-                JsonSourceGenerationOptionsAttribute options,
+                SourceGenerationOptionsSpec? options,
                 out bool hasExtensionDataProperty)
             {
                 List<PropertyGenerationSpec> properties = new();
@@ -845,7 +937,7 @@ namespace System.Text.Json.SourceGeneration
                 ISymbol memberInfo,
                 ref bool typeHasExtensionDataProperty,
                 JsonSourceGenerationMode? generationMode,
-                JsonSourceGenerationOptionsAttribute options)
+                SourceGenerationOptionsSpec? options)
             {
                 Debug.Assert(memberInfo is IFieldSymbol or IPropertySymbol);
 
@@ -903,9 +995,8 @@ namespace System.Text.Json.SourceGeneration
                     return null;
                 }
 
-                string clrName = memberInfo.Name;
-                string runtimePropertyName = DetermineRuntimePropName(clrName, jsonPropertyName, options.PropertyNamingPolicy);
-                string propertyNameVarName = DeterminePropNameIdentifier(runtimePropertyName);
+                string effectiveJsonPropertyName = DetermineEffectiveJsonPropertyName(memberInfo.Name, jsonPropertyName, options);
+                string propertyNameFieldName = DeterminePropertyNameFieldName(effectiveJsonPropertyName);
 
                 // Enqueue the property type for generation, unless the member is ignored.
                 TypeRef propertyTypeRef = ignoreCondition != JsonIgnoreCondition.Always
@@ -920,8 +1011,8 @@ namespace System.Text.Json.SourceGeneration
                     IsPublic = isAccessible,
                     IsVirtual = memberInfo.IsVirtual(),
                     JsonPropertyName = jsonPropertyName,
-                    RuntimePropertyName = runtimePropertyName,
-                    PropertyNameVarName = propertyNameVarName,
+                    EffectiveJsonPropertyName = effectiveJsonPropertyName,
+                    PropertyNameFieldName = propertyNameFieldName,
                     IsReadOnly = isReadOnly,
                     IsRequired = isRequired,
                     HasJsonRequiredAttribute = hasJsonRequiredAttribute,
@@ -976,7 +1067,7 @@ namespace System.Text.Json.SourceGeneration
 
                     if (converterType is null && _knownSymbols.JsonConverterAttributeType.IsAssignableFrom(attributeType))
                     {
-                        converterType = GetConverterTypeFromAttribute(contextType, memberInfo, attributeData);
+                        converterType = GetConverterTypeFromJsonConverterAttribute(contextType, memberInfo, attributeData);
                     }
                     else if (attributeType.ContainingAssembly.Name == SystemTextJsonNamespace)
                     {
@@ -1244,14 +1335,18 @@ namespace System.Text.Json.SourceGeneration
                 return propertyInitializers;
             }
 
-            private TypeRef? GetConverterTypeFromAttribute(INamedTypeSymbol contextType, ISymbol declaringSymbol, AttributeData attributeData)
+            private TypeRef? GetConverterTypeFromJsonConverterAttribute(INamedTypeSymbol contextType, ISymbol declaringSymbol, AttributeData attributeData)
             {
                 Debug.Assert(_knownSymbols.JsonConverterAttributeType.IsAssignableFrom(attributeData.AttributeClass));
-                var converterType = (INamedTypeSymbol?)attributeData.ConstructorArguments[0].Value;
+                var converterType = (ITypeSymbol?)attributeData.ConstructorArguments[0].Value;
+                return GetConverterTypeFromAttribute(contextType, converterType, declaringSymbol, attributeData);
+            }
 
-                if (converterType == null ||
-                    !_knownSymbols.JsonConverterType.IsAssignableFrom(converterType) ||
-                    !converterType.Constructors.Any(c => c.Parameters.Length == 0 && IsSymbolAccessibleWithin(c, within: contextType)))
+            private TypeRef? GetConverterTypeFromAttribute(INamedTypeSymbol contextType, ITypeSymbol? converterType, ISymbol declaringSymbol, AttributeData attributeData)
+            {
+                if (converterType is not INamedTypeSymbol namedConverterType ||
+                    !_knownSymbols.JsonConverterType.IsAssignableFrom(namedConverterType) ||
+                    !namedConverterType.Constructors.Any(c => c.Parameters.Length == 0 && IsSymbolAccessibleWithin(c, within: contextType)))
                 {
                     ReportDiagnostic(DiagnosticDescriptors.JsonConverterAttributeInvalidType, attributeData.GetDiagnosticLocation(), converterType?.ToDisplayString() ?? "null", declaringSymbol.ToDisplayString());
                     return null;
@@ -1265,30 +1360,24 @@ namespace System.Text.Json.SourceGeneration
                 return new TypeRef(converterType);
             }
 
-            private static string DetermineRuntimePropName(string clrPropName, string? jsonPropName, JsonKnownNamingPolicy namingPolicy)
+            private static string DetermineEffectiveJsonPropertyName(string propertyName, string? jsonPropertyName, SourceGenerationOptionsSpec? options)
             {
-                string runtimePropName;
-
-                if (jsonPropName != null)
+                if (jsonPropertyName != null)
                 {
-                    runtimePropName = jsonPropName;
+                    return jsonPropertyName;
                 }
-                else
-                {
-                    JsonNamingPolicy? instance = namingPolicy switch
-                    {
-                        JsonKnownNamingPolicy.CamelCase => JsonNamingPolicy.CamelCase,
-                        JsonKnownNamingPolicy.SnakeCaseLower => JsonNamingPolicy.SnakeCaseLower,
-                        JsonKnownNamingPolicy.SnakeCaseUpper => JsonNamingPolicy.SnakeCaseUpper,
-                        JsonKnownNamingPolicy.KebabCaseLower => JsonNamingPolicy.KebabCaseLower,
-                        JsonKnownNamingPolicy.KebabCaseUpper => JsonNamingPolicy.KebabCaseUpper,
-                        _ => null,
-                    };
 
-                    runtimePropName = instance?.ConvertName(clrPropName) ?? clrPropName;
-                }
+                JsonNamingPolicy? instance = options?.GetEffectivePropertyNamingPolicy() switch
+                {
+                    JsonKnownNamingPolicy.CamelCase => JsonNamingPolicy.CamelCase,
+                    JsonKnownNamingPolicy.SnakeCaseLower => JsonNamingPolicy.SnakeCaseLower,
+                    JsonKnownNamingPolicy.SnakeCaseUpper => JsonNamingPolicy.SnakeCaseUpper,
+                    JsonKnownNamingPolicy.KebabCaseLower => JsonNamingPolicy.KebabCaseLower,
+                    JsonKnownNamingPolicy.KebabCaseUpper => JsonNamingPolicy.KebabCaseUpper,
+                    _ => null,
+                };
 
-                return runtimePropName;
+                return instance?.ConvertName(propertyName) ?? propertyName;
             }
 
             private static string? DetermineImmutableCollectionFactoryMethod(string? immutableCollectionFactoryTypeFullName)
@@ -1296,7 +1385,7 @@ namespace System.Text.Json.SourceGeneration
                 return immutableCollectionFactoryTypeFullName is not null ? $"global::{immutableCollectionFactoryTypeFullName}.CreateRange" : null;
             }
 
-            private static string DeterminePropNameIdentifier(string runtimePropName)
+            private static string DeterminePropertyNameFieldName(string effectiveJsonPropertyName)
             {
                 const string PropName = "PropName_";
 
@@ -1304,16 +1393,16 @@ namespace System.Text.Json.SourceGeneration
                 // the rare case there is a C# property in a hex format.
                 const string EncodedPropName = "EncodedPropName_";
 
-                if (SyntaxFacts.IsValidIdentifier(runtimePropName))
+                if (SyntaxFacts.IsValidIdentifier(effectiveJsonPropertyName))
                 {
-                    return PropName + runtimePropName;
+                    return PropName + effectiveJsonPropertyName;
                 }
 
                 // Encode the string to a byte[] and then convert to hexadecimal.
                 // To make the generated code more readable, we could use a different strategy in the future
                 // such as including the full class name + the CLR property name when there are duplicates,
                 // but that will create unnecessary JsonEncodedText properties.
-                byte[] utf8Json = Encoding.UTF8.GetBytes(runtimePropName);
+                byte[] utf8Json = Encoding.UTF8.GetBytes(effectiveJsonPropertyName);
 
                 StringBuilder sb = new StringBuilder(
                     EncodedPropName,
index d7e2745..598d78b 100644 (file)
@@ -35,16 +35,6 @@ namespace System.Text.Json.SourceGeneration
 
         public required ImmutableEquatableArray<string> ContextClassDeclarations { get; init; }
 
-        public required JsonIgnoreCondition DefaultIgnoreCondition { get; init; }
-
-        public required bool IgnoreReadOnlyFields { get; init; }
-
-        public required bool IgnoreReadOnlyProperties { get; init; }
-
-        public required bool IncludeFields { get; init; }
-
-        public required JsonKnownNamingPolicy PropertyNamingPolicy { get; init; }
-
-        public required bool WriteIndented { get; init; }
+        public required SourceGenerationOptionsSpec? GeneratedOptionsSpec { get; init; }
     }
 }
index 054e1ae..577e921 100644 (file)
@@ -57,9 +57,12 @@ namespace System.Text.Json.SourceGeneration
         /// specified ahead-of-time via <see cref="JsonSourceGenerationOptionsAttribute"/>.
         /// Only used in fast-path serialization logic.
         /// </summary>
-        public required string RuntimePropertyName { get; init; }
+        public required string EffectiveJsonPropertyName { get; init; }
 
-        public required string PropertyNameVarName { get; init; }
+        /// <summary>
+        /// The field identifier used for storing JsonEncodedText for use by the fast-path serializer.
+        /// </summary>
+        public required string PropertyNameFieldName { get; init; }
 
         /// <summary>
         /// Whether the property has a set method.
@@ -156,7 +159,7 @@ namespace System.Text.Json.SourceGeneration
             }
 
             // Discard fields when JsonInclude or IncludeFields aren't enabled.
-            if (!IsProperty && !HasJsonInclude && !contextSpec.IncludeFields)
+            if (!IsProperty && !HasJsonInclude && contextSpec.GeneratedOptionsSpec?.IncludeFields != true)
             {
                 return false;
             }
@@ -166,12 +169,12 @@ namespace System.Text.Json.SourceGeneration
             {
                 if (IsProperty)
                 {
-                    if (contextSpec.IgnoreReadOnlyProperties)
+                    if (contextSpec.GeneratedOptionsSpec?.IgnoreReadOnlyProperties == true)
                     {
                         return false;
                     }
                 }
-                else if (contextSpec.IgnoreReadOnlyFields)
+                else if (contextSpec.GeneratedOptionsSpec?.IgnoreReadOnlyFields == true)
                 {
                     return false;
                 }
diff --git a/src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs b/src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs
new file mode 100644 (file)
index 0000000..e1ddf2e
--- /dev/null
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text.Json.Serialization;
+
+namespace System.Text.Json.SourceGeneration
+{
+    /// <summary>
+    /// Models compile-time configuration of <see cref="JsonSourceGenerationOptionsAttribute"/>.
+    /// Properties are made nullable to model the presence or absence of a given configuration.
+    /// </summary>
+    public sealed record SourceGenerationOptionsSpec
+    {
+        public required JsonSourceGenerationMode? GenerationMode { get; init; }
+
+        public required JsonSerializerDefaults? Defaults { get; init; }
+
+        public required bool? AllowTrailingCommas { get; init; }
+
+        public required ImmutableEquatableArray<TypeRef>? Converters { get; init; }
+
+        public required int? DefaultBufferSize { get; init; }
+
+        public required JsonIgnoreCondition? DefaultIgnoreCondition { get; init; }
+
+        public required JsonKnownNamingPolicy? DictionaryKeyPolicy { get; init; }
+
+        public required bool? IgnoreReadOnlyFields { get; init; }
+
+        public required bool? IgnoreReadOnlyProperties { get; init; }
+
+        public required bool? IncludeFields { get; init; }
+
+        public required int? MaxDepth { get; init; }
+
+        public required JsonNumberHandling? NumberHandling { get; init; }
+
+        public required JsonObjectCreationHandling? PreferredObjectCreationHandling { get; init; }
+
+        public required bool? PropertyNameCaseInsensitive { get; init; }
+
+        public required JsonKnownNamingPolicy? PropertyNamingPolicy { get; init; }
+
+        public required JsonCommentHandling? ReadCommentHandling { get; init; }
+
+        public required JsonUnknownTypeHandling? UnknownTypeHandling { get; init; }
+
+        public required JsonUnmappedMemberHandling? UnmappedMemberHandling { get; init; }
+
+        public required bool? WriteIndented { get; init; }
+
+        public JsonKnownNamingPolicy? GetEffectivePropertyNamingPolicy()
+            => PropertyNamingPolicy ?? (Defaults is JsonSerializerDefaults.Web ? JsonKnownNamingPolicy.CamelCase : null);
+    }
+}
index 06fccb0..e14905d 100644 (file)
@@ -31,6 +31,7 @@
     <Compile Include="..\Common\JsonCamelCaseNamingPolicy.cs" Link="Common\System\Text\Json\JsonCamelCaseNamingPolicy.cs" />
     <Compile Include="..\Common\JsonNamingPolicy.cs" Link="Common\System\Text\Json\JsonNamingPolicy.cs" />
     <Compile Include="..\Common\JsonAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonAttribute.cs" />
+    <Compile Include="..\Common\JsonCommentHandling.cs" Link="Common\System\Text\Json\JsonCommentHandling.cs" />
     <Compile Include="..\Common\JsonConstants.cs" Link="Common\System\Text\Json\JsonConstants.cs" />
     <Compile Include="..\Common\JsonHelpers.cs" Link="Common\System\Text\Json\JsonHelpers.cs" />
     <Compile Include="..\Common\JsonIgnoreCondition.cs" Link="Common\System\Text\Json\Serialization\JsonIgnoreCondition.cs" />
     <Compile Include="..\Common\JsonObjectCreationHandling.cs" Link="Common\System\Text\Json\Serialization\JsonObjectCreationHandling.cs" />
     <Compile Include="..\Common\JsonSeparatorNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonSeparatorNamingPolicy.cs" />
     <Compile Include="..\Common\JsonSerializableAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonSerializableAttribute.cs" />
+    <Compile Include="..\Common\JsonSerializerDefaults.cs" Link="Common\System\Text\Json\Serialization\JsonSerializerDefaults.cs" />
     <Compile Include="..\Common\JsonSnakeCaseLowerNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonSnakeCaseLowerNamingPolicy.cs" />
     <Compile Include="..\Common\JsonSnakeCaseUpperNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonSnakeCaseUpperNamingPolicy.cs" />
     <Compile Include="..\Common\JsonSourceGenerationMode.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationMode.cs" />
     <Compile Include="..\Common\JsonSourceGenerationOptionsAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationOptionsAttribute.cs" />
+    <Compile Include="..\Common\JsonUnknownTypeHandling.cs" Link="Common\System\Text\Json\Serialization\JsonUnknownTypeHandling.cs" />
     <Compile Include="..\Common\JsonUnmappedMemberHandling.cs" Link="Common\System\Text\Json\Serialization\JsonUnmappedMemberHandling.cs" />
     <Compile Include="..\Common\ThrowHelper.cs" Link="Common\System\Text\Json\ThrowHelper.cs" />
     <Compile Include="$(CommonPath)\Roslyn\GetBestTypeByMetadataName.cs" Link="Common\Roslyn\GetBestTypeByMetadataName.cs" />
@@ -67,6 +70,7 @@
     <Compile Include="Model\ParameterGenerationSpec.cs" />
     <Compile Include="Model\PropertyGenerationSpec.cs" />
     <Compile Include="Model\PropertyInitializerGenerationSpec.cs" />
+    <Compile Include="Model\SourceGenerationOptionsSpec.cs" />
     <Compile Include="Model\TypeGenerationSpec.cs" />
     <Compile Include="Model\TypeRef.cs" />
   </ItemGroup>
index 15300ab..46feae5 100644 (file)
@@ -1045,12 +1045,24 @@ namespace System.Text.Json.Serialization
     public sealed partial class JsonSourceGenerationOptionsAttribute : System.Text.Json.Serialization.JsonAttribute
     {
         public JsonSourceGenerationOptionsAttribute() { }
+        public JsonSourceGenerationOptionsAttribute(System.Text.Json.JsonSerializerDefaults defaults) { }
+        public bool AllowTrailingCommas { get { throw null; } set { } }
+        public System.Type[]? Converters { get { throw null; } set { } }
+        public int DefaultBufferSize { get { throw null; } set { } }
         public System.Text.Json.Serialization.JsonIgnoreCondition DefaultIgnoreCondition { get { throw null; } set { } }
+        public System.Text.Json.Serialization.JsonKnownNamingPolicy DictionaryKeyPolicy { get { throw null; } set { } }
         public System.Text.Json.Serialization.JsonSourceGenerationMode GenerationMode { get { throw null; } set { } }
         public bool IgnoreReadOnlyFields { get { throw null; } set { } }
         public bool IgnoreReadOnlyProperties { get { throw null; } set { } }
         public bool IncludeFields { get { throw null; } set { } }
+        public int MaxDepth { get { throw null; } set { } }
+        public System.Text.Json.Serialization.JsonNumberHandling NumberHandling { get { throw null; } set { } }
+        public System.Text.Json.Serialization.JsonObjectCreationHandling PreferredObjectCreationHandling { get { throw null; } set { } }
+        public bool PropertyNameCaseInsensitive { get { throw null; } set { } }
         public System.Text.Json.Serialization.JsonKnownNamingPolicy PropertyNamingPolicy { get { throw null; } set { } }
+        public System.Text.Json.JsonCommentHandling ReadCommentHandling { get { throw null; } set { } }
+        public System.Text.Json.Serialization.JsonUnknownTypeHandling UnknownTypeHandling { get { throw null; } set { } }
+        public System.Text.Json.Serialization.JsonUnmappedMemberHandling UnmappedMemberHandling { 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. Applications should use the generic JsonStringEnumConverter<TEnum> instead.")]
index 49b9be5..8b5b4cf 100644 (file)
@@ -28,6 +28,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
     <Compile Include="..\Common\JsonCamelCaseNamingPolicy.cs" Link="Common\System\Text\Json\JsonCamelCaseNamingPolicy.cs" />
     <Compile Include="..\Common\JsonNamingPolicy.cs" Link="Common\System\Text\Json\JsonNamingPolicy.cs" />
     <Compile Include="..\Common\JsonAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonAttribute.cs" />
+    <Compile Include="..\Common\JsonCommentHandling.cs" Link="Common\System\Text\Json\JsonCommentHandling.cs" />
     <Compile Include="..\Common\JsonConstants.cs" Link="Common\System\Text\Json\JsonConstants.cs" />
     <Compile Include="..\Common\JsonHelpers.cs" Link="Common\System\Text\Json\JsonHelpers.cs" />
     <Compile Include="..\Common\JsonIgnoreCondition.cs" Link="Common\System\Text\Json\Serialization\JsonIgnoreCondition.cs" />
@@ -39,10 +40,12 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
     <Compile Include="..\Common\JsonUnmappedMemberHandling.cs" Link="Common\System\Text\Json\Serialization\JsonUnmappedMemberHandling.cs" />
     <Compile Include="..\Common\JsonSeparatorNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonSeparatorNamingPolicy.cs" />
     <Compile Include="..\Common\JsonSerializableAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonSerializableAttribute.cs" />
+    <Compile Include="..\Common\JsonSerializerDefaults.cs" Link="Common\System\Text\Json\Serialization\JsonSerializerDefaults.cs" />
     <Compile Include="..\Common\JsonSnakeCaseLowerNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonSnakeCaseLowerNamingPolicy.cs" />
     <Compile Include="..\Common\JsonSnakeCaseUpperNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonSnakeCaseUpperNamingPolicy.cs" />
     <Compile Include="..\Common\JsonSourceGenerationMode.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationMode.cs" />
     <Compile Include="..\Common\JsonSourceGenerationOptionsAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationOptionsAttribute.cs" />
+    <Compile Include="..\Common\JsonUnknownTypeHandling.cs" Link="Common\System\Text\Json\Serialization\JsonUnknownTypeHandling.cs" />
     <Compile Include="..\Common\ReflectionExtensions.cs" Link="Common\System\Text\Json\Serialization\ReflectionExtensions.cs" />
     <Compile Include="..\Common\ThrowHelper.cs" Link="Common\System\Text\Json\ThrowHelper.cs" />
     <Compile Include="System\Text\Json\AppContextSwitchHelper.cs" />
@@ -61,7 +64,6 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
     <Compile Include="System\Text\Json\Document\JsonElement.Parse.cs" />
     <Compile Include="System\Text\Json\Document\JsonProperty.cs" />
     <Compile Include="System\Text\Json\Document\JsonValueKind.cs" />
-    <Compile Include="System\Text\Json\JsonCommentHandling.cs" />
     <Compile Include="System\Text\Json\JsonConstants.cs" />
     <Compile Include="System\Text\Json\JsonEncodedText.cs" />
     <Compile Include="System\Text\Json\JsonException.cs" />
@@ -249,12 +251,10 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
     <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.Stream.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.String.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.Utf8JsonWriter.cs" />
-    <Compile Include="System\Text\Json\Serialization\JsonSerializerDefaults.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonSerializerOptions.Caching.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonSerializerOptions.Converters.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonSerializerOptions.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonStringEnumConverter.cs" />
-    <Compile Include="System\Text\Json\Serialization\JsonUnknownTypeHandling.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\FSharpCoreReflectionProxy.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonCollectionInfoValuesOfTCollection.cs" />
     <Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.Collections.cs" />
index bb77e52..b3564a7 100644 (file)
@@ -55,17 +55,11 @@ namespace System.Text.Json.Serialization
 
             JsonSerializerOptions? generatedSerializerOptions = GeneratedSerializerOptions;
 
-            if (ReferenceEquals(options, generatedSerializerOptions))
-            {
-                // Fast path for the 99% case
-                return true;
-            }
-
             return
                 generatedSerializerOptions is not null &&
                 // Guard against unsupported features
                 options.Converters.Count == 0 &&
-                options.Encoder == null &&
+                options.Encoder is null &&
                 // Disallow custom number handling we'd need to honor when writing.
                 // AllowReadingFromString and Strict are fine since there's no action to take when writing.
                 !JsonHelpers.RequiresSpecialNumberHandlingOnWrite(options.NumberHandling) &&
@@ -80,8 +74,7 @@ namespace System.Text.Json.Serialization
                 options.IgnoreReadOnlyProperties == generatedSerializerOptions.IgnoreReadOnlyProperties &&
                 options.IncludeFields == generatedSerializerOptions.IncludeFields &&
                 options.PropertyNamingPolicy == generatedSerializerOptions.PropertyNamingPolicy &&
-                options.DictionaryKeyPolicy == generatedSerializerOptions.DictionaryKeyPolicy &&
-                options.WriteIndented == generatedSerializerOptions.WriteIndented;
+                options.DictionaryKeyPolicy is null;
         }
 
         /// <summary>
index 7a5f2f8..72b3e42 100644 (file)
@@ -139,6 +139,8 @@ namespace System.Text.Json
         /// <param name="defaults"> The <see cref="JsonSerializerDefaults"/> to reason about.</param>
         public JsonSerializerOptions(JsonSerializerDefaults defaults) : this()
         {
+            // Should be kept in sync with equivalent overload in JsonSourceGenerationOptionsAttribute
+
             if (defaults == JsonSerializerDefaults.Web)
             {
                 _propertyNameCaseInsensitive = true;
index 2428e97..23b66ab 100644 (file)
@@ -4,6 +4,9 @@
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Linq;
+using System.Reflection;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
 using System.Text.RegularExpressions;
 using System.Threading.Tasks;
 using Xunit;
@@ -144,6 +147,39 @@ namespace System.Text.Json
             }
         }
 
+        public static void AssertOptionsEqual(JsonSerializerOptions expected, JsonSerializerOptions actual)
+        {
+            foreach (PropertyInfo property in typeof(JsonSerializerOptions).GetProperties(BindingFlags.Public | BindingFlags.Instance))
+            {
+                Type propertyType = property.PropertyType;
+
+                if (property.Name == nameof(JsonSerializerOptions.IsReadOnly))
+                {
+                    continue; // readonly-ness is not a structural property of JsonSerializerOptions.
+                }
+                else if (propertyType == typeof(IList<JsonConverter>))
+                {
+                    var expectedConverters = (IList<JsonConverter>)property.GetValue(expected);
+                    var actualConverters = (IList<JsonConverter>)property.GetValue(actual);
+                    Assert.Equal(expectedConverters.Count, actualConverters.Count);
+                    for (int i = 0; i < actualConverters.Count; i++)
+                    {
+                        Assert.IsType(expectedConverters[i].GetType(), actualConverters[i]);
+                    }
+                }
+                else if (propertyType == typeof(IList<IJsonTypeInfoResolver>))
+                {
+                    var list1 = (IList<IJsonTypeInfoResolver>)property.GetValue(expected);
+                    var list2 = (IList<IJsonTypeInfoResolver>)property.GetValue(actual);
+                    Assert.Equal(list1, list2);
+                }
+                else
+                {
+                    Assert.Equal(property.GetValue(expected), property.GetValue(actual));
+                }
+            }
+        }
+
         /// <summary>
         /// Linq Cartesian product
         /// </summary>
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs
new file mode 100644 (file)
index 0000000..005c7c5
--- /dev/null
@@ -0,0 +1,125 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Reflection;
+using System.Text.Json.Serialization;
+using Xunit;
+
+namespace System.Text.Json.SourceGeneration.Tests
+{
+    public static partial class JsonSourceGenerationOptionsTests
+    {
+        [Fact]
+        public static void ContextWithGeneralSerializerDefaults_GeneratesExpectedOptions()
+        {
+            JsonSerializerOptions expected = new(JsonSerializerDefaults.General) { TypeInfoResolver = ContextWithGeneralSerializerDefaults.Default };
+            JsonSerializerOptions options = ContextWithGeneralSerializerDefaults.Default.Options;
+
+            JsonTestHelper.AssertOptionsEqual(expected, options);
+        }
+
+        [JsonSourceGenerationOptions(JsonSerializerDefaults.General)]
+        [JsonSerializable(typeof(PersonStruct))]
+        public partial class ContextWithGeneralSerializerDefaults : JsonSerializerContext
+        { }
+
+        [Fact]
+        public static void ContextWithWebSerializerDefaults_GeneratesExpectedOptions()
+        {
+            JsonSerializerOptions expected = new(JsonSerializerDefaults.Web) { TypeInfoResolver = ContextWithWebSerializerDefaults.Default };
+            JsonSerializerOptions options = ContextWithWebSerializerDefaults.Default.Options;
+
+            JsonTestHelper.AssertOptionsEqual(expected, options);
+        }
+
+        [JsonSourceGenerationOptions(JsonSerializerDefaults.Web)]
+        [JsonSerializable(typeof(PersonStruct))]
+        public partial class ContextWithWebSerializerDefaults : JsonSerializerContext
+        { }
+
+        [Fact]
+        public static void ContextWithWebDefaultsAndOverriddenPropertyNamingPolicy_GeneratesExpectedOptions()
+        {
+            JsonSerializerOptions expected = new(JsonSerializerDefaults.Web)
+            {
+                PropertyNamingPolicy = JsonNamingPolicy.KebabCaseLower,
+                TypeInfoResolver = ContextWithWebDefaultsAndOverriddenPropertyNamingPolicy.Default,
+            };
+
+            JsonSerializerOptions options = ContextWithWebDefaultsAndOverriddenPropertyNamingPolicy.Default.Options;
+
+            JsonTestHelper.AssertOptionsEqual(expected, options);
+        }
+
+        [JsonSourceGenerationOptions(JsonSerializerDefaults.Web, PropertyNamingPolicy = JsonKnownNamingPolicy.KebabCaseLower)]
+        [JsonSerializable(typeof(PersonStruct))]
+        public partial class ContextWithWebDefaultsAndOverriddenPropertyNamingPolicy : JsonSerializerContext
+        { }
+
+        [Fact]
+        public static void ContextWithAllOptionsSet_GeneratesExpectedOptions()
+        {
+            JsonSerializerOptions expected = new(JsonSerializerDefaults.Web)
+            {
+                AllowTrailingCommas = true,
+                Converters = { new JsonStringEnumConverter<BindingFlags>(), new JsonStringEnumConverter<JsonIgnoreCondition>() },
+                DefaultBufferSize = 128,
+                DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
+                DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseUpper,
+                IgnoreReadOnlyFields = true,
+                IgnoreReadOnlyProperties = true,
+                IncludeFields = true,
+                MaxDepth = 1024,
+                NumberHandling = JsonNumberHandling.WriteAsString,
+                PreferredObjectCreationHandling = JsonObjectCreationHandling.Replace,
+                PropertyNameCaseInsensitive = true,
+                PropertyNamingPolicy = JsonNamingPolicy.KebabCaseUpper,
+                ReadCommentHandling = JsonCommentHandling.Skip,
+                UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode,
+                UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
+                WriteIndented = true,
+
+                TypeInfoResolver = ContextWithAllOptionsSet.Default,
+            };
+
+            JsonSerializerOptions options = ContextWithAllOptionsSet.Default.Options;
+
+            JsonTestHelper.AssertOptionsEqual(expected, options);
+        }
+
+        [JsonSourceGenerationOptions(JsonSerializerDefaults.Web,
+            AllowTrailingCommas = true,
+            Converters = new[] { typeof(JsonStringEnumConverter<BindingFlags>), typeof(JsonStringEnumConverter<JsonIgnoreCondition>) },
+            DefaultBufferSize = 128,
+            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
+            DictionaryKeyPolicy = JsonKnownNamingPolicy.SnakeCaseUpper,
+            IgnoreReadOnlyFields = true,
+            IgnoreReadOnlyProperties = true,
+            IncludeFields = true,
+            MaxDepth = 1024,
+            NumberHandling = JsonNumberHandling.WriteAsString,
+            PreferredObjectCreationHandling = JsonObjectCreationHandling.Replace,
+            PropertyNameCaseInsensitive = true,
+            PropertyNamingPolicy = JsonKnownNamingPolicy.KebabCaseUpper,
+            ReadCommentHandling = JsonCommentHandling.Skip,
+            UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode,
+            UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
+            WriteIndented = true)]
+        [JsonSerializable(typeof(PersonStruct))]
+        public partial class ContextWithAllOptionsSet : JsonSerializerContext
+        { }
+
+        [Fact]
+        public static void ContextWithInvalidSerializerDefaults_ThrowsArgumentOutOfRangeException()
+        {
+            TypeInitializationException ex = Assert.Throws<TypeInitializationException>(() => ContextWithInvalidSerializerDefaults.Default);
+            ArgumentOutOfRangeException inner = Assert.IsType<ArgumentOutOfRangeException>(ex.InnerException);
+            Assert.Contains("defaults", inner.Message);
+        }
+
+        [JsonSourceGenerationOptions((JsonSerializerDefaults)(-1))]
+        [JsonSerializable(typeof(PersonStruct))]
+        public partial class ContextWithInvalidSerializerDefaults : JsonSerializerContext
+        { }
+    }
+}
index c401cbe..75373ca 100644 (file)
     <Compile Include="Serialization\CollectionTests.cs" />
     <Compile Include="Serialization\ConstructorTests.cs" />
     <Compile Include="Serialization\ExtensionDataTests.cs" />
+    <Compile Include="JsonSourceGenerationOptionsTests.cs" />
     <Compile Include="Serialization\JsonCreationHandlingTests.cs" />
     <Compile Include="Serialization\ReferenceHandlerTests.cs" />
     <Compile Include="Serialization\ReferenceHandlerTests.IgnoreCycles.cs" />
index 575cd0e..7c4621f 100644 (file)
@@ -860,7 +860,7 @@ namespace System.Text.Json.Serialization.Tests
 
             var newOptions = new JsonSerializerOptions(options);
             Assert.False(newOptions.IsReadOnly);
-            VerifyOptionsEqual(options, newOptions);
+            JsonTestHelper.AssertOptionsEqual(options, newOptions);
 
             // No exception is thrown on mutating the new options instance because it is "unlocked".
             newOptions.ReferenceHandler = ReferenceHandler.Preserve;
@@ -902,7 +902,7 @@ namespace System.Text.Json.Serialization.Tests
         {
             JsonSerializerOptions options = GetFullyPopulatedOptionsInstance();
             var newOptions = new JsonSerializerOptions(options);
-            VerifyOptionsEqual(options, newOptions);
+            JsonTestHelper.AssertOptionsEqual(options, newOptions);
         }
 
         [Fact]
@@ -937,7 +937,7 @@ namespace System.Text.Json.Serialization.Tests
         {
             var options = new JsonSerializerOptions { TypeInfoResolver = JsonSerializerOptions.Default.TypeInfoResolver };
             JsonSerializerOptions optionsSingleton = JsonSerializerOptions.Default;
-            VerifyOptionsEqual(options, optionsSingleton);
+            JsonTestHelper.AssertOptionsEqual(options, optionsSingleton);
         }
 
         [Fact]
@@ -1176,7 +1176,7 @@ namespace System.Text.Json.Serialization.Tests
             var options = new JsonSerializerOptions();
             var newOptions = new JsonSerializerOptions(JsonSerializerDefaults.General);
             Assert.False(newOptions.IsReadOnly);
-            VerifyOptionsEqual(options, newOptions);
+            JsonTestHelper.AssertOptionsEqual(options, newOptions);
         }
 
         [Fact]
@@ -1262,45 +1262,12 @@ namespace System.Text.Json.Serialization.Tests
             return options;
         }
 
-        private static void VerifyOptionsEqual(JsonSerializerOptions options, JsonSerializerOptions newOptions)
-        {
-            foreach (PropertyInfo property in typeof(JsonSerializerOptions).GetProperties(BindingFlags.Public | BindingFlags.Instance))
-            {
-                Type propertyType = property.PropertyType;
-
-                if (property.Name == nameof(JsonSerializerOptions.IsReadOnly))
-                {
-                    continue; // readonly-ness is not a structural property of JsonSerializerOptions.
-                }
-                else if (propertyType == typeof(IList<JsonConverter>))
-                {
-                    var list1 = (IList<JsonConverter>)property.GetValue(options);
-                    var list2 = (IList<JsonConverter>)property.GetValue(newOptions);
-                    Assert.Equal(list1, list2);
-                }
-                else if (propertyType == typeof(IList<IJsonTypeInfoResolver>))
-                {
-                    var list1 = (IList<IJsonTypeInfoResolver>)property.GetValue(options);
-                    var list2 = (IList<IJsonTypeInfoResolver>)property.GetValue(newOptions);
-                    Assert.Equal(list1, list2);
-                }
-                else if (propertyType.IsValueType)
-                {
-                    Assert.Equal(property.GetValue(options), property.GetValue(newOptions));
-                }
-                else
-                {
-                    Assert.Same(property.GetValue(options), property.GetValue(newOptions));
-                }
-            }
-        }
-
         [Fact]
         public static void CopyConstructor_IgnoreNullValuesCopied()
         {
             var options = new JsonSerializerOptions { IgnoreNullValues = true };
             var newOptions = new JsonSerializerOptions(options);
-            VerifyOptionsEqual(options, newOptions);
+            JsonTestHelper.AssertOptionsEqual(options, newOptions);
         }
 
         [Fact]