diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PropertyRef.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PropertyRef.cs index aabcfdd2ee5f9f..91f9f18332ecbd 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PropertyRef.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PropertyRef.cs @@ -41,8 +41,10 @@ internal readonly struct PropertyRef(ulong key, JsonPropertyInfo? info, byte[] u [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ReadOnlySpan propertyName, ulong key) { - // If the property name is less than 8 bytes, it is embedded in the key so no further comparison is necessary. - return key == Key && (propertyName.Length <= PropertyNameKeyLength || propertyName.SequenceEqual(Utf8PropertyName)); + // If both property names are less than 8 bytes, they are embedded in the key so no further comparison is necessary. + return key == Key && + ((propertyName.Length <= PropertyNameKeyLength && Utf8PropertyName.Length <= PropertyNameKeyLength) + || propertyName.SequenceEqual(Utf8PropertyName)); } /// diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CacheTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CacheTests.cs index fb9079c916672f..1d5d5836b8dacd 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CacheTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CacheTests.cs @@ -165,6 +165,56 @@ public static void PropertyCacheWithMinInputsLast() JsonSerializer.Deserialize(json, options); } + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(5)] + [InlineData(6)] + [InlineData(7)] + public static void PropertyCache_NamesWithSameKeyButDifferentLength_AreDistinct(int shortNameLength) + { + string shortName = new('a', shortNameLength); + string longName = + shortName + + new string('\0', 7 - shortNameLength) + + new string('b', 249 + shortNameLength); + + Assert.Equal(256, longName.Length - shortName.Length); + + var options = new JsonSerializerOptions + { + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = + { + // Customize the name so the theory can exercise every length with one POCO type. + typeInfo => + { + if (typeInfo.Type == typeof(PropertyKeyLengthPoco)) + { + typeInfo.Properties[0].Name = shortName; + } + } + } + } + }; + + string json = JsonSerializer.Serialize(new Dictionary { [longName] = "42" }); + Assert.Null(JsonSerializer.Deserialize(json, options).Value); + + json = JsonSerializer.Serialize(new Dictionary { [shortName] = "42" }); + Assert.Equal("42", JsonSerializer.Deserialize(json, options).Value); + } + + private class PropertyKeyLengthPoco + { + // The declared name is irrelevant because the resolver replaces it. + public string Value { get; set; } + } + // Use a common options instance to encourage additional metadata collisions across types. Also since // this options is not the default options instance the tests will not use previously cached metadata. private static JsonSerializerOptions s_options = new JsonSerializerOptions { IncludeFields = true };