Skip to content

Commit

Permalink
Fix case-insensitive deserialization of default enum values
Browse files Browse the repository at this point in the history
  • Loading branch information
PranavSenthilnathan committed Jan 31, 2025
1 parent a73f163 commit 9377c71
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ public void AppendConflictingField(EnumFieldInfo other)
{
Debug.Assert(JsonName.Equals(other.JsonName, StringComparison.OrdinalIgnoreCase), "The conflicting entry must be equal up to case insensitivity.");

if (Kind is EnumFieldNameKind.Default || JsonName.Equals(other.JsonName, StringComparison.Ordinal))
if (MatchesSupersetOf(this, other))
{
// Silently discard if the preceding entry is the default or has identical name.
return;
Expand All @@ -611,13 +611,34 @@ public void AppendConflictingField(EnumFieldInfo other)
// Walk the existing list to ensure we do not add duplicates.
foreach (EnumFieldInfo conflictingField in conflictingFields)
{
if (conflictingField.Kind is EnumFieldNameKind.Default || conflictingField.JsonName.Equals(other.JsonName, StringComparison.Ordinal))
if (MatchesSupersetOf(conflictingField, other))
{
return;
}
}

conflictingFields.Add(other);

// Determines whether the first field info matches everything that the second field info matches,
// in which case the second field info is redundant and doesn't need to be added to the list.
static bool MatchesSupersetOf(EnumFieldInfo field1, EnumFieldInfo field2)
{
// The default name matches everything case-insensitively.
if (field1.Kind is EnumFieldNameKind.Default)
{
return true;
}

// field1 matches case-sensitively since it's not the default name.
// field2 matches case-insensitively, so it matches more than field1.
if (field2.Kind is EnumFieldNameKind.Default)
{
return false;
}

// Both are case-sensitive so they need to be identical.
return field1.JsonName.Equals(field2.JsonName, StringComparison.Ordinal);
}
}

public EnumFieldInfo? GetMatchingField(ReadOnlySpan<char> input)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1207,5 +1207,55 @@ public enum EnumWithInvalidMemberName6
[JsonStringEnumMemberName("Comma separators not allowed, in flags enums")]
Value
}

[Theory]
[InlineData("\"cAmElCaSe\"", EnumWithVaryingNamingPolicies.camelCase, JsonKnownNamingPolicy.SnakeCaseUpper)]
[InlineData("\"cAmElCaSe\"", EnumWithVaryingNamingPolicies.camelCase, JsonKnownNamingPolicy.SnakeCaseLower)]
[InlineData("\"cAmElCaSe\"", EnumWithVaryingNamingPolicies.camelCase, JsonKnownNamingPolicy.KebabCaseUpper)]
[InlineData("\"cAmElCaSe\"", EnumWithVaryingNamingPolicies.camelCase, JsonKnownNamingPolicy.KebabCaseLower)]
[InlineData("\"pAsCaLcAsE\"", EnumWithVaryingNamingPolicies.PascalCase, JsonKnownNamingPolicy.SnakeCaseUpper)]
[InlineData("\"pAsCaLcAsE\"", EnumWithVaryingNamingPolicies.PascalCase, JsonKnownNamingPolicy.SnakeCaseLower)]
[InlineData("\"pAsCaLcAsE\"", EnumWithVaryingNamingPolicies.PascalCase, JsonKnownNamingPolicy.KebabCaseUpper)]
[InlineData("\"pAsCaLcAsE\"", EnumWithVaryingNamingPolicies.PascalCase, JsonKnownNamingPolicy.KebabCaseLower)]
[InlineData("\"sNaKe_CaSe_UpPeR\"", EnumWithVaryingNamingPolicies.SNAKE_CASE_UPPER, JsonKnownNamingPolicy.SnakeCaseUpper)]
[InlineData("\"sNaKe_CaSe_LoWeR\"", EnumWithVaryingNamingPolicies.snake_case_lower, JsonKnownNamingPolicy.SnakeCaseLower)]
[InlineData("\"cAmElCaSe\"", EnumWithVaryingNamingPolicies.camelCase, JsonKnownNamingPolicy.CamelCase)]
[InlineData("\"a\"", EnumWithVaryingNamingPolicies.A, JsonKnownNamingPolicy.CamelCase)]
[InlineData("\"a\"", EnumWithVaryingNamingPolicies.A, JsonKnownNamingPolicy.SnakeCaseUpper)]
[InlineData("\"a\"", EnumWithVaryingNamingPolicies.A, JsonKnownNamingPolicy.SnakeCaseLower)]
[InlineData("\"a\"", EnumWithVaryingNamingPolicies.A, JsonKnownNamingPolicy.KebabCaseUpper)]
[InlineData("\"a\"", EnumWithVaryingNamingPolicies.A, JsonKnownNamingPolicy.KebabCaseLower)]
[InlineData("\"B\"", EnumWithVaryingNamingPolicies.b, JsonKnownNamingPolicy.CamelCase)]
[InlineData("\"B\"", EnumWithVaryingNamingPolicies.b, JsonKnownNamingPolicy.SnakeCaseUpper)]
[InlineData("\"B\"", EnumWithVaryingNamingPolicies.b, JsonKnownNamingPolicy.SnakeCaseLower)]
[InlineData("\"B\"", EnumWithVaryingNamingPolicies.b, JsonKnownNamingPolicy.KebabCaseUpper)]
[InlineData("\"B\"", EnumWithVaryingNamingPolicies.b, JsonKnownNamingPolicy.KebabCaseLower)]
public static void StringConverterWithNamingPolicyIsCaseInsensitive(string json, EnumWithVaryingNamingPolicies expectedValue, JsonKnownNamingPolicy namingPolicy)
{
JsonNamingPolicy policy = namingPolicy switch
{
JsonKnownNamingPolicy.CamelCase => JsonNamingPolicy.CamelCase,
JsonKnownNamingPolicy.SnakeCaseLower => JsonNamingPolicy.SnakeCaseLower,
JsonKnownNamingPolicy.SnakeCaseUpper => JsonNamingPolicy.SnakeCaseUpper,
JsonKnownNamingPolicy.KebabCaseLower => JsonNamingPolicy.KebabCaseLower,
JsonKnownNamingPolicy.KebabCaseUpper => JsonNamingPolicy.KebabCaseUpper,
_ => throw new ArgumentOutOfRangeException(nameof(namingPolicy)),
};

JsonSerializerOptions options = new() { Converters = { new JsonStringEnumConverter(policy) } };

EnumWithVaryingNamingPolicies value = JsonSerializer.Deserialize<EnumWithVaryingNamingPolicies>(json, options);
Assert.Equal(expectedValue, value);
}

public enum EnumWithVaryingNamingPolicies
{
SNAKE_CASE_UPPER,
snake_case_lower,
camelCase,
PascalCase,
A,
b,
}
}
}

0 comments on commit 9377c71

Please sign in to comment.