Skip to content
Merged
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
73 changes: 57 additions & 16 deletions src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json.Serialization.Tests;
using System.Text.Json.Serialization.Tests.Schemas.OrderPayload;
using System.Text.RegularExpressions;
Expand All @@ -17,59 +18,99 @@ public static void AssertJsonEqual(string expected, string actual)
{
using JsonDocument expectedDom = JsonDocument.Parse(expected);
using JsonDocument actualDom = JsonDocument.Parse(actual);
AssertJsonEqual(expectedDom.RootElement, actualDom.RootElement);
AssertJsonEqual(expectedDom.RootElement, actualDom.RootElement, new());
}

private static void AssertJsonEqual(JsonElement expected, JsonElement actual)
private static void AssertJsonEqual(JsonElement expected, JsonElement actual, Stack<object> path)
{
JsonValueKind valueKind = expected.ValueKind;
Assert.Equal(valueKind, actual.ValueKind);
AssertTrue(passCondition: valueKind == actual.ValueKind);

switch (valueKind)
{
case JsonValueKind.Object:
var propertyNames = new HashSet<string>();

var expectedProperties = new List<string>();
foreach (JsonProperty property in expected.EnumerateObject())
{
propertyNames.Add(property.Name);
expectedProperties.Add(property.Name);
}

var actualProperties = new List<string>();
foreach (JsonProperty property in actual.EnumerateObject())
{
propertyNames.Add(property.Name);
actualProperties.Add(property.Name);
}

foreach (string name in propertyNames)
foreach (var property in expectedProperties.Except(actualProperties))
{
AssertJsonEqual(expected.GetProperty(name), actual.GetProperty(name));
AssertTrue(passCondition: false, $"Property \"{property}\" missing from actual object.");
}

foreach (var property in actualProperties.Except(expectedProperties))
{
AssertTrue(passCondition: false, $"Actual object defines additional property \"{property}\".");
}

foreach (string name in expectedProperties)
{
path.Push(name);
AssertJsonEqual(expected.GetProperty(name), actual.GetProperty(name), path);
path.Pop();
}
break;
case JsonValueKind.Array:
JsonElement.ArrayEnumerator expectedEnumerator = actual.EnumerateArray();
JsonElement.ArrayEnumerator actualEnumerator = expected.EnumerateArray();
JsonElement.ArrayEnumerator expectedEnumerator = expected.EnumerateArray();
JsonElement.ArrayEnumerator actualEnumerator = actual.EnumerateArray();

int i = 0;
while (expectedEnumerator.MoveNext())
{
Assert.True(actualEnumerator.MoveNext());
AssertJsonEqual(expectedEnumerator.Current, actualEnumerator.Current);
AssertTrue(passCondition: actualEnumerator.MoveNext(), "Actual array contains fewer elements.");
path.Push(i++);
AssertJsonEqual(expectedEnumerator.Current, actualEnumerator.Current, path);
path.Pop();
}

Assert.False(actualEnumerator.MoveNext());
AssertTrue(passCondition: !actualEnumerator.MoveNext(), "Actual array contains additional elements.");
break;
case JsonValueKind.String:
Assert.Equal(expected.GetString(), actual.GetString());
AssertTrue(passCondition: expected.GetString() == actual.GetString());
break;
case JsonValueKind.Number:
case JsonValueKind.True:
case JsonValueKind.False:
case JsonValueKind.Null:
Assert.Equal(expected.GetRawText(), actual.GetRawText());
AssertTrue(passCondition: expected.GetRawText() == actual.GetRawText());
break;
default:
Debug.Fail($"Unexpected JsonValueKind: JsonValueKind.{valueKind}.");
break;
}

void AssertTrue(bool passCondition, string? message = null)
{
if (!passCondition)
{
message ??= "Expected JSON does not match actual value";
Assert.Fail($"{message}\nExpected JSON: {expected}\n Actual JSON: {actual}\n in JsonPath: {BuildJsonPath(path)}");
}

// TODO replace with JsonPath implementation for JsonElement
// cf. https://github.com/dotnet/runtime/issues/31068
static string BuildJsonPath(Stack<object> path)
{
var sb = new StringBuilder("$");
foreach (object node in path.Reverse())
{
string pathNode = node is string propertyName
? "." + propertyName
: $"[{(int)node}]";

sb.Append(pathNode);
}
return sb.ToString();
}
}
}

public static async Task<List<T>> ToListAsync<T>(this IAsyncEnumerable<T> source)
Expand Down