JSON Assertions¶
Axiom.Json is the optional Axiom package for deterministic JSON assertions.
It focuses on:
- structural JSON equivalency
- simple JSON path existence checks
- simple scalar value-at-path checks
- simple object and array shape checks at a path
It does not add HTTP helpers, ASP.NET helpers, direct Newtonsoft.Json support, or a full JSONPath engine.
If your subject is HttpResponseMessage rather than raw JSON content, use HTTP and API assertions. Axiom.Http reuses Axiom.Json internally for response-body JSON checks.
Install¶
dotnet add package Axiom.Json
Use it alongside Axiom.Assertions:
using Axiom.Assertions;
using Axiom.Json;
Supported Inputs¶
The package supports:
- raw JSON
string JsonDocumentJsonElement
Examples:
using System.Text.Json;
using Axiom.Assertions;
using Axiom.Json;
var actualJson = """
{ "id": 1, "name": "Bob", "roles": ["admin", "author"] }
""";
var expectedJson = """
{ "roles": ["admin", "author"], "name": "Bob", "id": 1.0 }
""";
actualJson.Should().BeJsonEquivalentTo(expectedJson);
using var document = JsonDocument.Parse(actualJson);
document.Should().HaveJsonPath("$.roles[1]");
document.RootElement.Should().HaveJsonStringAtPath("$.name", "Bob");
Structural Equivalency¶
Use BeJsonEquivalentTo(...) and NotBeJsonEquivalentTo(...) when you want JSON-aware structural comparison instead of plain string equality.
using Axiom.Assertions;
using Axiom.Json;
var actualJson = """
{
"customer": {
"id": 7,
"active": true,
"roles": ["admin", "author"]
}
}
""";
var expectedJson = """
{
"customer": {
"roles": ["admin", "author"],
"active": true,
"id": 7.0
}
}
""";
actualJson.Should().BeJsonEquivalentTo(expectedJson);
actualJson.Should().NotBeJsonEquivalentTo("""
{ "customer": { "id": 9, "active": true, "roles": ["admin", "author"] } }
""");
Current semantics:
- object property order does not matter
- array order does matter
- missing properties and extra properties fail explicitly
- value-kind mismatches fail explicitly
- numeric comparison uses normalized JSON numeric values, so
1,1.0, and1e0are treated as equivalent
JSON Path Assertions¶
Axiom.Json uses a deliberately small path syntax:
- optional root marker:
$ - object-member traversal with
. - array-index traversal with
[index]
Examples:
$.customer.id$.customer.roles[0]items[1].sku
This is not a full JSONPath implementation. Property names that need escaping are out of scope for the current path model.
using Axiom.Assertions;
using Axiom.Json;
var json = """
{
"customer": {
"id": 7,
"name": "Bob",
"active": true,
"roles": ["admin", "author"],
"deletedAt": null
}
}
""";
json.Should().HaveJsonPath("$.customer.roles[1]");
json.Should().NotHaveJsonPath("$.customer.email");
json.Should().HaveJsonObjectAtPath("$.customer");
json.Should().HaveJsonArrayAtPath("$.customer.roles");
json.Should().HaveJsonArrayLengthAtPath("$.customer.roles", 2);
json.Should().HaveJsonPropertyCountAtPath("$.customer", 5);
json.Should().HaveJsonStringAtPath("$.customer.name", "Bob");
json.Should().HaveJsonNumberAtPath("$.customer.id", 7m);
json.Should().HaveJsonBooleanAtPath("$.customer.active", true);
json.Should().HaveJsonNullAtPath("$.customer.deletedAt");
Use HaveJsonObjectAtPath(...), HaveJsonArrayAtPath(...), and the count checks when the shape of the JSON at a path matters before checking scalar values inside it.
Contract Assertions¶
Use contract assertions when the JSON shape matters but full schema validation would be too much. These checks stay method-based and use the same simple path syntax as the rest of Axiom.Json.
using Axiom.Assertions;
using Axiom.Json;
var responseJson = """
{
"id": "evt_123",
"type": "order.created",
"status": "queued",
"customer": {
"id": "cus_123",
"name": "Bob"
},
"items": [
{ "id": "ord_1", "status": "queued" },
{ "id": "ord_2", "status": "processing" }
],
"statuses": ["queued", "processing"]
}
""";
responseJson.Should().BeValidJson();
responseJson.Should().HaveJsonProperties("id", "type", "status", "customer", "items");
responseJson.Should().HaveJsonPropertiesAtPath("$.customer", "id", "name");
responseJson.Should().HaveAllowedValueAtPath("$.status", "queued", "processing", "complete");
responseJson.Should().HaveJsonObjectItemsWithPropertiesAtPath("$.items", "id", "status");
responseJson.Should().HaveJsonObjectItemsWithOnlyPropertiesAtPath("$.items", "id", "status");
responseJson.Should().HaveAllowedValuesAtPath("$.statuses", "queued", "processing", "complete");
For shared allowed-value sets, pass a collection:
using Axiom.Assertions;
using Axiom.Json;
var responseJson = """{ "status": "queued" }""";
var allowedStatuses = new[] { "queued", "processing", "complete" };
responseJson.Should().HaveAllowedValueAtPath("$.status", allowedStatuses);
Invalid JSON¶
For raw JSON string subjects, invalid JSON fails clearly with an invalid JSON parse location in the failure message.
Invalid expected JSON passed as a raw string is treated as an invalid assertion argument and throws ArgumentException.
Current Limits¶
Axiom.Json is intentionally narrow:
System.Text.Jsoninputs plus raw JSON strings only- no direct
Newtonsoft.Jsonsupport - no HTTP or API-response helpers in this package; use
Axiom.HttpforHttpResponseMessage - no full JSONPath language
- no wildcard selection; array-wide checks operate on paths that resolve to arrays, such as
$.items - array order is significant
For the full method list, see the Assertion Reference.