From 59edaad404d1b8e47080015ae8d0787f94c970df Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 13:21:59 -0700 Subject: [PATCH] [release/8.0] Honor JsonSerializerOptions.PropertyNameCaseInsensitive in property name conflict resolution. (#93935) * Honor JsonSerializerOptions.PropertyNameCaseInsensitive in property name conflict resolution. * Update src/libraries/System.Text.Json/tests/Common/PropertyNameTests.cs Co-authored-by: Jeff Handley * Address feedback --------- Co-authored-by: Eirik Tsarpalis Co-authored-by: Jeff Handley --- .../gen/JsonSourceGenerator.Parser.cs | 7 ++--- .../DefaultJsonTypeInfoResolver.Helpers.cs | 2 +- .../Metadata/JsonMetadataServices.Helpers.cs | 2 +- .../Serialization/Metadata/JsonTypeInfo.cs | 5 ++-- .../tests/Common/PropertyNameTests.cs | 29 +++++++++++++++++++ .../Serialization/PropertyNameTests.cs | 2 ++ 6 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index e0dac6a9ad82c..594f7ad9770c3 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -863,7 +863,7 @@ private List ParsePropertyGenerationSpecs( { Location? typeLocation = typeToGenerate.Location; List properties = new(); - PropertyHierarchyResolutionState state = new(); + PropertyHierarchyResolutionState state = new(options); hasExtensionDataProperty = false; // Walk the type hierarchy starting from the current type up to the base type(s) @@ -970,11 +970,10 @@ bool PropertyIsOverriddenAndIgnored(IPropertySymbol property, Dictionary Properties = new(); - public Dictionary AddedProperties = new(); + public Dictionary AddedProperties = new(options?.PropertyNameCaseInsensitive == true ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); public Dictionary? IgnoredMembers; public bool IsPropertyOrderSpecified; public bool HasInvalidConfigurationForFastPath; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs index c654a920f8ff6..3f929d8737848 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs @@ -88,7 +88,7 @@ private static void PopulateProperties(JsonTypeInfo typeInfo) bool constructorHasSetsRequiredMembersAttribute = typeInfo.Converter.ConstructorInfo?.HasSetsRequiredMembersAttribute() ?? false; - JsonTypeInfo.PropertyHierarchyResolutionState state = new(); + JsonTypeInfo.PropertyHierarchyResolutionState state = new(typeInfo.Options); // Walk the type hierarchy starting from the current type up to the base type(s) foreach (Type currentType in typeInfo.Type.GetSortedTypeHierarchy()) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Helpers.cs index 1b7113f9dd758..965b4cea39570 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Helpers.cs @@ -137,7 +137,7 @@ internal static void PopulateProperties(JsonTypeInfo typeInfo, JsonTypeInfo.Json // Regardless of the source generator we need to re-run the naming conflict resolution algorithm // at run time since it is possible that the naming policy or other configs can be different then. - JsonTypeInfo.PropertyHierarchyResolutionState state = new(); + JsonTypeInfo.PropertyHierarchyResolutionState state = new(typeInfo.Options); foreach (JsonPropertyInfo jsonPropertyInfo in properties) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs index a1a184f6059d2..668e0c7b15e1a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs @@ -992,10 +992,9 @@ public JsonPropertyInfo CreateJsonPropertyInfo(Type propertyType, string name) internal abstract ValueTask DeserializeAsObjectAsync(Stream utf8Json, CancellationToken cancellationToken); internal abstract object? DeserializeAsObject(Stream utf8Json); - internal ref struct PropertyHierarchyResolutionState + internal ref struct PropertyHierarchyResolutionState(JsonSerializerOptions options) { - public PropertyHierarchyResolutionState() { } - public Dictionary AddedProperties = new(); + public Dictionary AddedProperties = new(options.PropertyNameCaseInsensitive ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); public Dictionary? IgnoredProperties; public bool IsPropertyOrderSpecified; } diff --git a/src/libraries/System.Text.Json/tests/Common/PropertyNameTests.cs b/src/libraries/System.Text.Json/tests/Common/PropertyNameTests.cs index 4295359c6f038..021481ae5a136 100644 --- a/src/libraries/System.Text.Json/tests/Common/PropertyNameTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/PropertyNameTests.cs @@ -494,5 +494,34 @@ public class ClassWithSpecialCharacters [JsonPropertyName("\uA000_2")] // Valid C# property name: \uA000_2 public int YiIt_2 { get; set; } } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task ClassWithIgnoredCaseInsensitiveConflict_RespectsIgnoredMember(bool propertyNameCaseInsensitive) + { + // Regression test for https://github.com/dotnet/runtime/issues/93903 + // specifically for propertyNameCaseInsensitive := true + + JsonSerializerOptions options = Serializer.CreateOptions(makeReadOnly: false); + options.PropertyNameCaseInsensitive = propertyNameCaseInsensitive; + + var value = new ClassWithIgnoredCaseInsensitiveConflict { name = "lowercase", Name = "uppercase" }; + string json = await Serializer.SerializeWrapper(value, options); + + Assert.Equal("""{"name":"lowercase"}""", json); + + value = await Serializer.DeserializeWrapper(json, options); + Assert.Equal("lowercase", value.name); + Assert.Null(value.Name); + } + + public class ClassWithIgnoredCaseInsensitiveConflict + { + public string name { get; set; } + + [JsonIgnore] + public string Name { get; set; } + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyNameTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyNameTests.cs index 82566bf7123ce..e512451eed72b 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyNameTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyNameTests.cs @@ -28,6 +28,7 @@ public PropertyNameTests_Metadata() [JsonSerializable(typeof(ObjectPropertyNamesDifferentByCaseOnly_TestClass))] [JsonSerializable(typeof(OverridePropertyNameDesignTime_TestClass))] [JsonSerializable(typeof(SimpleTestClass))] + [JsonSerializable(typeof(ClassWithIgnoredCaseInsensitiveConflict))] internal sealed partial class PropertyNameTestsContext_Metadata : JsonSerializerContext { } @@ -53,6 +54,7 @@ public PropertyNameTests_Default() [JsonSerializable(typeof(ObjectPropertyNamesDifferentByCaseOnly_TestClass))] [JsonSerializable(typeof(OverridePropertyNameDesignTime_TestClass))] [JsonSerializable(typeof(SimpleTestClass))] + [JsonSerializable(typeof(ClassWithIgnoredCaseInsensitiveConflict))] internal sealed partial class PropertyNameTestsContext_Default : JsonSerializerContext { }