Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Honor JsonSerializerOptions.PropertyNameCaseInsensitive in property name conflict resolution. #93933

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,7 @@ private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGener
{
Location? typeLocation = typeToGenerate.Location;
List<PropertyGenerationSpec> 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)
Expand Down Expand Up @@ -970,11 +970,10 @@ bool PropertyIsOverriddenAndIgnored(IPropertySymbol property, Dictionary<string,
}
}

private ref struct PropertyHierarchyResolutionState
private ref struct PropertyHierarchyResolutionState(SourceGenerationOptionsSpec? options)
{
public PropertyHierarchyResolutionState() { }
public readonly List<int> Properties = new();
public Dictionary<string, (PropertyGenerationSpec, ISymbol, int index)> AddedProperties = new();
public Dictionary<string, (PropertyGenerationSpec, ISymbol, int index)> AddedProperties = new(options?.PropertyNameCaseInsensitive == true ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
public Dictionary<string, ISymbol>? IgnoredMembers;
public bool IsPropertyOrderSpecified;
public bool HasInvalidConfigurationForFastPath;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -992,10 +992,9 @@ public JsonPropertyInfo CreateJsonPropertyInfo(Type propertyType, string name)
internal abstract ValueTask<object?> 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<string, (JsonPropertyInfo, int index)> AddedProperties = new();
public Dictionary<string, (JsonPropertyInfo, int index)> AddedProperties = new(options.PropertyNameCaseInsensitive ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
public Dictionary<string, JsonPropertyInfo>? IgnoredProperties;
public bool IsPropertyOrderSpecified;
}
Expand Down
29 changes: 29 additions & 0 deletions src/libraries/System.Text.Json/tests/Common/PropertyNameTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ClassWithIgnoredCaseInsensitiveConflict>(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; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
}
Expand All @@ -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
{
}
Expand Down