-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
You can verify, that the `JsonElement` matches an expected object: ```csharp JsonElement subject = JsonDocument.Parse("{\"foo\": 1, \"bar\": \"baz\"}").RootElement; await Expect.That(subject).Should().Match(new{foo = 1}); await Expect.That(subject).Should().MatchExactly(new{foo = 1, bar = "baz"}); ``` You can verify, that the `JsonElement` matches an expected array: ```csharp JsonElement subject = JsonDocument.Parse("[1,2,3]").RootElement; await Expect.That(subject).Should().Match([1, 2]); await Expect.That(subject).Should().MatchExactly([1, 2, 3]); ``` You can also verify, that the `JsonElement` matches a primitive type: ```csharp await Expect.That(JsonDocument.Parse("\"foo\"").RootElement).Should().Match("foo"); await Expect.That(JsonDocument.Parse("42.3").RootElement).Should().Match(42.3); await Expect.That(JsonDocument.Parse("true").RootElement).Should().Match(true); await Expect.That(JsonDocument.Parse("null").RootElement).Should().Match(null); ```
- Loading branch information
Showing
15 changed files
with
1,945 additions
and
265 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,291 @@ | ||
#if NET8_0_OR_GREATER | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Text.Json; | ||
using aweXpect.Customization; | ||
|
||
namespace aweXpect.Json; | ||
|
||
/// <summary> | ||
/// Validates a <see cref="JsonElement" /> against an <see cref="object" />. | ||
/// </summary> | ||
internal static class JsonElementValidator | ||
{ | ||
public static JsonComparisonResult Compare( | ||
JsonElement actualElement, | ||
JsonElement expectedElement, | ||
JsonOptions options) | ||
=> Compare(new JsonComparisonResult(), "$", actualElement, expectedElement, options); | ||
|
||
private static JsonComparisonResult Compare( | ||
this JsonComparisonResult result, | ||
string path, | ||
JsonElement actualElement, | ||
JsonElement expectedElement, | ||
JsonOptions options) | ||
=> actualElement.ValueKind switch | ||
{ | ||
JsonValueKind.Array => result.CompareJsonArray(path, actualElement, expectedElement, options), | ||
JsonValueKind.False => result.CompareJsonBoolean(JsonValueKind.False, path, actualElement, expectedElement), | ||
JsonValueKind.True => result.CompareJsonBoolean(JsonValueKind.True, path, actualElement, expectedElement), | ||
JsonValueKind.Null => result.CompareJsonNull(path, actualElement, expectedElement), | ||
JsonValueKind.Number => result.CompareJsonNumber(path, actualElement, expectedElement), | ||
JsonValueKind.String => result.CompareJsonString(path, actualElement, expectedElement), | ||
JsonValueKind.Object => result.CompareJsonObject(path, actualElement, expectedElement, options), | ||
_ => throw new ArgumentOutOfRangeException($"Unsupported JsonValueKind: {actualElement.ValueKind}") | ||
}; | ||
|
||
private static JsonComparisonResult CompareJsonArray( | ||
this JsonComparisonResult result, | ||
string path, | ||
JsonElement actualElement, | ||
JsonElement expectedElement, | ||
JsonOptions options) | ||
{ | ||
if (expectedElement.ValueKind != JsonValueKind.Array) | ||
{ | ||
result.AddError(path, $"was {Format(actualElement, true)} instead of {Format(expectedElement)}"); | ||
return result; | ||
} | ||
|
||
for (int index = 0; index < expectedElement.GetArrayLength(); index++) | ||
{ | ||
string memberPath = path + "[" + index + "]"; | ||
JsonElement expectedArrayElement = expectedElement[index]; | ||
if (actualElement.GetArrayLength() <= index) | ||
{ | ||
result.AddError(memberPath, $"had missing {Format(expectedArrayElement)}"); | ||
continue; | ||
} | ||
|
||
JsonElement actualArrayElement = actualElement[index]; | ||
result.Compare(memberPath, actualArrayElement, expectedArrayElement, options); | ||
} | ||
|
||
if (!options.IgnoreAdditionalProperties) | ||
{ | ||
for (int index = expectedElement.GetArrayLength(); index < actualElement.GetArrayLength(); index++) | ||
{ | ||
JsonElement actualArrayElement = actualElement[index]; | ||
string memberPath = path + "[" + index + "]"; | ||
result.AddError(memberPath, $"had unexpected {Format(actualArrayElement)}"); | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
|
||
private static JsonComparisonResult CompareJsonBoolean( | ||
this JsonComparisonResult result, | ||
JsonValueKind valueKind, | ||
string path, | ||
JsonElement actualElement, | ||
JsonElement expectedElement) | ||
{ | ||
if (expectedElement.ValueKind != valueKind) | ||
{ | ||
result.AddError(path, expectedElement.ValueKind is JsonValueKind.False or JsonValueKind.True | ||
? $"was {Format(actualElement)} instead of {Format(expectedElement)}" | ||
: $"was {Format(actualElement, true)} instead of {Format(expectedElement)}"); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
private static JsonComparisonResult CompareJsonNull( | ||
this JsonComparisonResult result, | ||
string path, | ||
JsonElement actualElement, | ||
JsonElement expectedElement) | ||
{ | ||
if (expectedElement.ValueKind != JsonValueKind.Null) | ||
{ | ||
result.AddError(path, $"was {Format(actualElement, true)} instead of {Format(expectedElement)}"); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
private static JsonComparisonResult CompareJsonNumber( | ||
this JsonComparisonResult result, | ||
string path, | ||
JsonElement actualElement, | ||
JsonElement expectedElement) | ||
{ | ||
if (expectedElement.ValueKind != JsonValueKind.Number) | ||
{ | ||
result.AddError(path, $"was {Format(actualElement, true)} instead of {Format(expectedElement)}"); | ||
return result; | ||
} | ||
|
||
if (actualElement.TryGetInt32(out int v1) && expectedElement.TryGetInt32(out int v2)) | ||
{ | ||
if (v1 == v2) | ||
{ | ||
return result; | ||
} | ||
|
||
result.AddError(path, $"was {Format(actualElement)} instead of {Format(expectedElement)}"); | ||
return result; | ||
} | ||
|
||
if (actualElement.TryGetDouble(out double n1) && expectedElement.TryGetDouble(out double n2)) | ||
{ | ||
if (n1.Equals(n2)) | ||
{ | ||
return result; | ||
} | ||
|
||
result.AddError(path, $"was {Format(actualElement)} instead of {Format(expectedElement)}"); | ||
return result; | ||
} | ||
|
||
return result; | ||
} | ||
|
||
private static JsonComparisonResult CompareJsonObject( | ||
this JsonComparisonResult result, | ||
string path, | ||
JsonElement actualElement, | ||
JsonElement expectedElement, | ||
JsonOptions options) | ||
{ | ||
if (expectedElement.ValueKind != JsonValueKind.Object) | ||
{ | ||
result.AddError(path, $"was {Format(actualElement, true)} instead of {Format(expectedElement)}"); | ||
return result; | ||
} | ||
|
||
foreach (JsonProperty item in expectedElement.EnumerateObject()) | ||
{ | ||
string memberPath = path + "." + item.Name; | ||
if (!actualElement.TryGetProperty(item.Name, out JsonElement property)) | ||
{ | ||
result.AddError(memberPath, "was missing"); | ||
continue; | ||
} | ||
|
||
result.Compare(memberPath, property, item.Value, options); | ||
} | ||
|
||
if (!options.IgnoreAdditionalProperties) | ||
{ | ||
foreach (var property in actualElement.EnumerateObject()) | ||
{ | ||
string memberPath = path + "." + property.Name; | ||
if (result.HasMemberError(memberPath) || | ||
expectedElement.TryGetProperty(property.Name, out _)) | ||
{ | ||
continue; | ||
} | ||
|
||
result.AddError(memberPath, $"had unexpected {Format(property.Value)}"); | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
|
||
private static JsonComparisonResult CompareJsonString( | ||
this JsonComparisonResult result, | ||
string path, | ||
JsonElement actualElement, | ||
JsonElement expectedElement) | ||
{ | ||
if (expectedElement.ValueKind != JsonValueKind.String) | ||
{ | ||
result.AddError(path, $"was {Format(actualElement, true)} instead of {Format(expectedElement)}"); | ||
return result; | ||
} | ||
|
||
string? value1 = actualElement.GetString(); | ||
string? value2 = expectedElement.GetString(); | ||
if (value1 != value2) | ||
{ | ||
result.AddError(path, $"was {Format(actualElement)} instead of {Format(expectedElement)}"); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
private static string Format(JsonElement jsonElement, bool includeType = false) | ||
{ | ||
string GetKindName(JsonValueKind kind) | ||
=> kind switch | ||
{ | ||
JsonValueKind.False => "boolean", | ||
JsonValueKind.True => "boolean", | ||
JsonValueKind.Number => "number", | ||
JsonValueKind.String => "string", | ||
JsonValueKind.Array => "array", | ||
JsonValueKind.Object => "object", | ||
_ => "" | ||
}; | ||
|
||
if (jsonElement.ValueKind == JsonValueKind.Null) | ||
{ | ||
return "Null"; | ||
} | ||
|
||
if (jsonElement.ValueKind == JsonValueKind.String) | ||
{ | ||
return includeType | ||
? $"string \"{jsonElement}\"" | ||
: $"\"{jsonElement}\""; | ||
} | ||
|
||
return includeType | ||
? $"{GetKindName(jsonElement.ValueKind)} {jsonElement}" | ||
: jsonElement.ToString(); | ||
} | ||
|
||
|
||
internal class JsonComparisonResult | ||
{ | ||
private readonly Dictionary<string, string> _errors = new(); | ||
|
||
public bool HasError => _errors.Any(); | ||
|
||
public void AddError(string memberPath, string error) | ||
=> _errors.Add(memberPath, error); | ||
|
||
public bool HasMemberError(string memberPath) | ||
=> _errors.ContainsKey(memberPath); | ||
|
||
public override string ToString() | ||
{ | ||
StringBuilder sb = new(); | ||
if (_errors.Any()) | ||
{ | ||
sb.Append("differed as"); | ||
bool hasMoreThanOneDifference = _errors.Count > 1; | ||
int count = 0; | ||
foreach (KeyValuePair<string, string> differentMember in _errors) | ||
{ | ||
if (count++ >= Customize.Formatting.MaximumNumberOfCollectionItems) | ||
{ | ||
sb.AppendLine().Append(" … (") | ||
.Append(_errors.Count - Customize.Formatting.MaximumNumberOfCollectionItems) | ||
.Append(" more)"); | ||
return sb.ToString(); | ||
} | ||
|
||
if (hasMoreThanOneDifference) | ||
{ | ||
sb.AppendLine().Append(' '); | ||
} | ||
|
||
sb.Append(' ').Append(differentMember.Key).Append(' ').Append(differentMember.Value).Append(" and"); | ||
} | ||
} | ||
|
||
sb.Length -= 4; | ||
|
||
return sb.ToString(); | ||
} | ||
} | ||
} | ||
|
||
#endif |
Oops, something went wrong.