Skip to content
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 @@ -553,6 +553,13 @@ private static EnumFieldInfo[] ResolveEnumFields(JsonNamingPolicy? namingPolicy)
enumFields[i] = new EnumFieldInfo(key, kind, originalName, jsonName);
}

if (s_isFlagsEnum)
{
// Perform topological sort for flags enums to ensure values that are supersets of other values come first.
// This is important for flags enums to ensure proper parsing and formatting.
enumFields = TopologicalSortEnumFields(enumFields);
}

return enumFields;
}

Expand Down Expand Up @@ -659,6 +666,56 @@ static bool ConflictsWith(EnumFieldInfo current, EnumFieldInfo other)
}
}

/// <summary>
/// Performs a topological sort on enum fields to ensure values that are supersets of other values come first.
/// </summary>
private static EnumFieldInfo[] TopologicalSortEnumFields(EnumFieldInfo[] enumFields)
{
if (enumFields.Length <= 1)
{
return enumFields;
}

var indices = new int[enumFields.Length];
for (int i = 0; i < enumFields.Length; i++)
{
indices[i] = i;
}

Array.Sort(indices, (i, j) => GetComparisonKey(i).CompareTo(GetComparisonKey(j)));

var sortedFields = new EnumFieldInfo[enumFields.Length];
for (int i = 0; i < indices.Length; i++)
{
sortedFields[i] = enumFields[indices[i]];
}

return sortedFields;

(int PopCount, int Index) GetComparisonKey(int i)
{
// Sort by descending pop count of the enum value.
// Since Array.Sort isn't a stable algorithm
// append the current index to the comparison key.
return (-PopCount(enumFields[i].Key), i);
}

static int PopCount(ulong value)
{
#if NET
return (int)ulong.PopCount(value);
#else
int count = 0;
while (value != 0)
{
value &= value - 1;
count++;
}
return count;
#endif
}
}

private enum EnumFieldNameKind
{
Default = 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1257,5 +1257,41 @@ public enum EnumWithVaryingNamingPolicies
A,
b,
}

[Fact]
public static void EnumWithOverlappingBitsTests()
{
JsonSerializerOptions options = new() { Converters = { new JsonStringEnumConverter() } };

EnumWithOverlappingBits e1 = EnumWithOverlappingBits.BITS01 | EnumWithOverlappingBits.BIT3;
string json1 = JsonSerializer.Serialize(e1, options);
Assert.Equal("\"BITS01, BIT3\"", json1);

EnumWithOverlappingBits2 e2 = EnumWithOverlappingBits2.BITS01 | EnumWithOverlappingBits2.BIT3;
string json2 = JsonSerializer.Serialize(e2, options);
Assert.Equal("\"BITS01, BIT3\"", json2);
}

[Flags]
public enum EnumWithOverlappingBits
{
UNKNOWN = 0,
BIT0 = 1,
BIT1 = 2,
BIT2 = 4,
BIT3 = 8,
BITS01 = 3,
}

[Flags]
public enum EnumWithOverlappingBits2
{
UNKNOWN = 0,
BIT0 = 1,
// direct option for bit 1 missing
BIT2 = 4,
BIT3 = 8,
BITS01 = 3,
}
}
}
Loading