JsonAssertions.TUnit 0.2.0

Prefix Reserved
dotnet add package JsonAssertions.TUnit --version 0.2.0
                    
NuGet\Install-Package JsonAssertions.TUnit -Version 0.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="JsonAssertions.TUnit" Version="0.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="JsonAssertions.TUnit" Version="0.2.0" />
                    
Directory.Packages.props
<PackageReference Include="JsonAssertions.TUnit" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add JsonAssertions.TUnit --version 0.2.0
                    
#r "nuget: JsonAssertions.TUnit, 0.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package JsonAssertions.TUnit@0.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=JsonAssertions.TUnit&version=0.2.0
                    
Install as a Cake Addin
#tool nuget:?package=JsonAssertions.TUnit&version=0.2.0
                    
Install as a Cake Tool

JsonAssertions.TUnit

NuGet Downloads License: MIT .NET

Scope: Test projects only. Not intended for production code.

TUnit-native JSON assertions for .NET. Fluent entry points over TUnit's Assert.That(...) pipeline for asserting against System.Text.Json documents and HTTP response bodies. AOT-compatible, trimmable, no runtime reflection in the assertion path.

Full documentation, design notes, and roadmap: github.com/JohnVerheij/JsonAssertions.TUnit

Status: v0.2.0

Each entry point is available over a JSON string, a System.Text.Json.JsonElement, and an HttpResponseMessage (whose body is read as the JSON document).

Entry point Behaviour
HasJsonProperty(path) Asserts a property exists at the path.
DoesNotHaveJsonProperty(path) Asserts no property exists at the path.
HasJsonValue(path, expected) Asserts the value at path equals expected (a string, bool, or number).
HasJsonValueOneOf(path, T[]) Asserts the value at path is one of the given strings or numbers.
HasJsonValueMatching(path, predicate) Asserts the value at path satisfies Func<JsonElement, bool>.
HasJsonValueParsableAs<T>(path) Asserts the value at path is a JSON string parseable as T (where T : IParsable<T>).
HasJsonValueKind(path, kind) Asserts the value at path is of the given JsonValueKind.
HasJsonBoolean(path) Asserts the value at path is a JSON boolean (true or false).
HasNonEmptyJsonString(path) Asserts the value at path is a non-empty JSON string.
HasJsonArrayLength(path, length) Asserts the value at path is a JSON array of the given length.
HasNonEmptyJsonArray(path) / HasEmptyJsonArray(path) Asserts the value at path is a non-empty / empty JSON array.

The path is a dot-separated property navigation with optional [N] zero-based bracket indices and an optional leading $ JSONPath root reference: user.name, items[0].id, objects[0].planData[1].pickPlanId, $[0] for a root-array first element. See the path-syntax notes on GitHub for the full grammar.

The point over a hand-rolled TryGetProperty(...).IsTrue() helper is the failure message: every assertion renders a path-context block saying where resolution stopped, not merely that it did.

Install

dotnet add package JsonAssertions.TUnit

Requirements: TUnit 1.44.39 or later, .NET 10. System.Text.Json is in-box on .NET 10, so the package carries no runtime dependency beyond TUnit.

Quick start

using System.Text.Json;

[Test]
public async Task ResponseBodyHasExpectedShape(CancellationToken ct)
{
    string json = """{"user":{"name":"alice","age":30},"roles":["admin"]}""";

    await Assert.That(json).HasJsonProperty("user.name");
    await Assert.That(json).HasJsonValue("user.age", 30);
    await Assert.That(json).HasJsonArrayLength("roles", 1);
}

The fluent entry points auto-import via TUnit.Assertions.Extensions. The same entry points work on a JsonElement, and directly on an HttpResponseMessage:

// Reads the response body and asserts against it. The cancellation token flows to the read.
await Assert.That(response).HasJsonProperty("user.name", ct);
await Assert.That(response).HasJsonValue("user.age", 30, ct);

When an assertion fails, the message names the failure point:

to have a JSON property at path "user.address.city"
  resolved as far as: user.address
  no property "city" on "user.address"

A response body or string that is not valid JSON fails the assertion with an explained message rather than throwing a raw JsonException.

Two namespaces

The single package places types in two namespaces, the same shape as the rest of the assertion family:

Type Namespace Auto-imported?
JsonPath, JsonPathResolution, JsonValueComparison, JsonShape (framework-agnostic core) JsonAssertions No (needs using JsonAssertions;)
Source-generated assertion entry points TUnit.Assertions.Extensions Yes (TUnit auto-imports)

Roadmap

  • Deserialise-then-predicate assertions.
  • Semantic JSON equality and subset / fragment matching.

Family

Part of an assertion family for TUnit:

License

MIT. Copyright (c) 2026 John Verheij.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.0 259 5/15/2026
0.1.0 114 5/14/2026
0.0.1 112 5/14/2026

View the rendered release notes: https://github.com/JohnVerheij/JsonAssertions.TUnit/releases/tag/v0.2.0

### Added

- **Array-indexed path segments.** `JsonPath.Resolve(element, "items[0].name")` navigates `items` (object property) then index 0 (array element) then `name` (object property). Indices are zero-based, non-negative integers in `[N]` brackets. Mixed property + index segments compose freely (`objects[0].planData[1].pickPlanId`). Closes the #1 friction surfaced by the v0.1.0 adoption survey.
- **`$` JSONPath root-self.** `JsonPath.Resolve(element, "$")` resolves to the supplied element itself. `$.user.name` is equivalent to `user.name`; `$[0]` is equivalent to `[0]`. A bare `[0]` against a root array also works without the `$` prefix. Closes the "no path to assert root-array shape" gap surfaced by the v0.1.0 adoption survey.
- **`HasNonEmptyJsonString(path)`** on `string` and `JsonElement` (and `HttpResponseMessage` for the async HTTP entry point). Asserts the value at `path` is a JSON string of non-zero length. A non-string kind or an empty `""` string fails.
- **`HasJsonBoolean(path)`** on `string`, `JsonElement`, and `HttpResponseMessage`. Asserts the value at `path` is a JSON boolean (either `true` or `false`). `JsonValueKind.True` and `.False` are distinct kinds, so this is the discoverable form of "this field is a bool, either value" that `HasJsonValueKind` alone cannot express.
- **`HasJsonValueMatching(path, Func<JsonElement, bool> predicate)`** on `string`, `JsonElement`, and `HttpResponseMessage`. Asserts the value at `path` satisfies a consumer-supplied predicate. Covers the ~¼ of value assertions that are not exact-equality (numeric inequalities, complex shape checks).
- **`HasJsonValueOneOf(path, string[])`** and **`HasJsonValueOneOf(path, double[])`** on `string`, `JsonElement`, and `HttpResponseMessage`. The discoverable form for "value is one of {Healthy, Degraded, Unhealthy}" or "code is one of {200, 503, 504}". Callers pass a C# 12 collection-expression literal: `HasJsonValueOneOf("status", ["Healthy", "Degraded"])`.
- **`HasJsonValueParsableAs<T>(path) where T : IParsable<T>`** on `string`, `JsonElement`, and `HttpResponseMessage`. Asserts the value at `path` is a JSON string whose text parses as `T` via `T.TryParse(value, CultureInfo.InvariantCulture, out _)`. Covers the "value parses as `Guid` / `DateTimeOffset` / `Uri`" pattern without committing to a particular parser per call site.
- **`JsonShape.IsNonEmptyString(JsonElement)`** and **`JsonShape.IsBoolean(JsonElement)`** framework-agnostic predicates in the `JsonAssertions` core, matching the family pattern (core predicate + TUnit-adapter assertion).
- **`JsonValueComparison.MatchesAny(JsonElement, string[])`** and **`JsonValueComparison.MatchesAny(JsonElement, double[])`** framework-agnostic comparison primitives in the `JsonAssertions` core.
- **`Directory.Build.targets` auto-extracts the per-version section from `CHANGELOG.md` at pack time** and feeds it into `<PackageReleaseNotes>`, so the Release Notes tab on the nuget.org package page shows the per-version body verbatim rather than a literal placeholder. Affects releases from this version onward; nupkgs already on nuget.org are immutable.
- **Prepended `View the rendered release notes: <url>` line** on the extracted body, pointing at the matching GitHub Release. nuget.org renders the Release Notes tab as plaintext with preserved line breaks rather than rendered markdown ([NuGet/NuGetGallery#8889](https://github.com/NuGet/NuGetGallery/issues/8889) is the open feature request); the prepended URL gives consumers a one-click route to the rendered-markdown version on GitHub.

### Changed

- **`JsonPath.Resolve` failure-point context for array failures.** An out-of-range index on an array, or an index access on a non-array, now surfaces in `FailedSegment` as `[N]` (matching the resolved-prefix syntax) and renders a tailored reason line in the failure message: `no element at index [N] on "items"` for an array out-of-range; `cannot index [N]: "user" is an Object, not an array` for an index access on a non-array.
- **`<PackageReleaseNotes>` fallback** in `JsonAssertions.TUnit.csproj` is now `$(RepositoryUrl)/releases/tag/v$(Version)` rather than the literal text "See CHANGELOG.md". The URL is auto-linked by nuget.org, so the no-match case still gives consumers a one-click route to the matching GitHub Release notes.
- **`CONVENTIONS.md` updated to v0.5.** Adds a `CHANGELOG conventions` section (Keep a Changelog 1.1.0 standard headers, user-facing-only content, header order, stylistic rules) and a `PackageReleaseNotes` auto-extract convention. Supersedes the v0.4 bump that added `JsonAssertions.TUnit` to the family roster; the v0.5 file remains copied identically across all five family repos.
- **`CODE_OF_CONDUCT.md` upgraded to Contributor Covenant v3.0** from v2.1. The maintainer contact link is now the GitHub profile URL (https://github.com/JohnVerheij) rather than a private email address, since GitHub keeps the primary email private.
- **`PackageValidationBaselineVersion` set to `0.1.0`.** ApiCompat now validates the additive v0.2.0 surface against the v0.1.0 baseline; `CompatibilitySuppressions.xml` is regenerated to capture the accepted additive differences from v0.1.0.
- **Package description** revised to drop the v0.0.1 / v0.1.0 sequencing narrative and describe the current shipped surface verbatim.