Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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,80 @@ static bool ConflictsWith(EnumFieldInfo current, EnumFieldInfo other)
}
}

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

// Build adjacency list and in-degree count
// Edge from A to B means A should come before B (B contains A's bits)
List<int>[] adjacencyList = new List<int>[n];
int[] inDegree = new int[n];

for (int i = 0; i < n; i++)
{
adjacencyList[i] = new List<int>();
}

// Create edges: if value A is a superset of value B's bits, A should come before B
for (int i = 0; i < n; i++)
{
ulong iKey = enumFields[i].Key;
for (int j = 0; j < n; j++)
{
if (i != j)
{
ulong jKey = enumFields[j].Key;
// Check if i's bits are a superset of j's bits (and not equal)
if (iKey != jKey && (iKey & jKey) == jKey)
{
adjacencyList[i].Add(j);
inDegree[j]++;
}
}
}
}

// Kahn's algorithm
Queue<int> queue = new Queue<int>();

// Enqueue all nodes with in-degree 0
for (int i = 0; i < n; i++)
{
if (inDegree[i] == 0)
{
queue.Enqueue(i);
}
}

EnumFieldInfo[] sortedFields = new EnumFieldInfo[n];
int sortedIndex = 0;

while (queue.Count > 0)
{
int current = queue.Dequeue();
sortedFields[sortedIndex++] = enumFields[current];

// Reduce in-degree for all neighbors
foreach (int neighbor in adjacencyList[current])
{
inDegree[neighbor]--;
if (inDegree[neighbor] == 0)
{
queue.Enqueue(neighbor);
}
}
}

return sortedFields;
}

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