Skip to content

Commit

Permalink
[release/8.0] Honor JsonSerializerOptions.PropertyNameCaseInsensitive…
Browse files Browse the repository at this point in the history
… 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 <jeffhandley@users.noreply.github.com>

* Address feedback

---------

Co-authored-by: Eirik Tsarpalis <eirik.tsarpalis@gmail.com>
Co-authored-by: Jeff Handley <jeffhandley@users.noreply.github.com>
  • Loading branch information
3 people committed Oct 24, 2023
1 parent 488a8a3 commit 59edaad
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 9 deletions.
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

0 comments on commit 59edaad

Please sign in to comment.