Skip to content

Development

m4bwav edited this page Sep 30, 2026 · 3 revisions

For anyone changing the library. AGENTS.md at the repository root holds the rules in full; this page is the tour.

Layout

Path What it holds
JsonPrettyPrinterPlus/ The library. JsonPrettyPrinter.cs has the extension methods and the public printer, PrettyPrintEngine.cs the internal formatter (one switch per character), JsonPrettyPrintOptions.cs the options record, JsonSerialization/JsonExtensions.cs the System.Text.Json helpers, and IsExternalInit.cs the one polyfill (it lets netstandard2.0 compile init setters).
tests/Golden/ The golden recordings of the published 3.0.1 (1,174 cases per runtime), the program that made them, the public API list, and the 1.0.1.1 and 2.1.1 recordings under upgrade/. Never edited.
tests/JsonPrettyPrinterPlus.GoldenTests/ Replays every recorded case against the library on net10.0 and net48 (64-bit) and checks every public API line.
tests/consumers/, tests/package/ Fresh consumer projects of the packed or published package, and the package content check.
JsonPrettyPrinterPlusTests/ 37 NUnit 4 tests, run on net10.0 and net48. The net48 leg executes the netstandard2.0 build of the library. TestFiles/jsonLintBeautifyExample.json is the reference output for the nested-object test.
benchmarks/JsonPrettyPrinterPlus.Benchmarks/ A BenchmarkDotNet project that pretty prints a generated 1 MB document. Not packed; its results folder is ignored by Git, and the numbers live in the changelog.
.github/workflows/ ci.yml builds, tests and checks the package; release.yml publishes; verify-published.yml checks a release from nuget.org. dependabot.yml proposes NuGet, SDK and action updates weekly after a seven-day cooldown.
ai-docs/ Plans, decisions, notes and the log from past work, for people and agents alike; INDEX.md lists them.
global.json Pins the SDK to 10.0.401 with rollForward: latestFeature.

The library is one assembly, JsonPrettyPrinterPlus.dll, 12.8 KB per target framework.

Build and test

dotnet restore --locked-mode
dotnet format --verify-no-changes
dotnet build -c Release
dotnet test -c Release
dotnet pack JsonPrettyPrinterPlus -c Release -o artifacts
dotnet run -c Release --project benchmarks/JsonPrettyPrinterPlus.Benchmarks

Lock files are committed for every project. After changing a package reference, run plain dotnet restore and commit the updated packages.lock.json. .editorconfig is enforced both by the format check and in the build (EnforceCodeStyleInBuild), warnings are errors, and the analyzers run at latest-recommended. Files are LF; .gitattributes says so, and the format check on the Windows runner depends on it.

The net48 tests execute only on Windows. On Linux and macOS, dotnet test builds the net48 leg (the reference assemblies package makes that possible) and runs the net10.0 one.

What the tests pin

Output for well-formed input is the contract. The suite covers the original four round-trip tests from 2014 and every bug fixed in 2.1.0: escapes, empty scopes at any depth, null input, stray and mismatched brackets, and state left behind by malformed input. It also pins idempotence, a JsonNode.DeepEquals round trip, each option, each overload and the serializer helpers. Behind it, the golden replay checks all 1,174 answers the published 3.0.1 gives on each runtime; its recordings are never edited, and CI fails if they change. A change to what PrettyPrintJson() writes needs a test and a changelog entry. A change that only affects broken input is at most a minor version.

Targets

The library multi-targets netstandard2.0 and net10.0. Nothing from .NET 5 or later goes in without an #if or a polyfill: no ArgumentNullException.ThrowIfNull, no HashCode. The net10.0 build is trimmable and AOT compatible, so any member that touches reflection-based System.Text.Json carries RequiresUnreferencedCode and RequiresDynamicCode under #if NET5_0_OR_GREATER and gets a JsonTypeInfo<T> overload beside it.

Benchmark

dotnet run -c Release --project benchmarks/JsonPrettyPrinterPlus.Benchmarks pretty prints a deterministic 1 MB minified document with nested objects, arrays, escapes and numbers, ten iterations after three warm-ups, with the memory diagnoser on. Put before and after numbers in CHANGELOG.md when a change touches the engine. Performance and threading has the history.

Continuous integration

ci.yml runs on every push to master and every pull request, on Ubuntu and Windows: the golden files are unchanged, locked restore, format check, build, a NuGet audit that fails on any advisory, the net10.0 tests (and on Windows the net48 tests), a pack with package validation against the last release, a check of the package's files and dependencies, and fresh consumer projects of the packed package. A final job named ci is the check the master branch requires. Actions are pinned to commit SHAs.

Releasing

Nothing reaches nuget.org without the maintainer. release.yml runs for a v* tag, which only repository admins can push. It checks that the tag equals <Version>, sits on master and has a green ci check, builds and tests on Linux and Windows, checks the package it will push and runs the consumers on it, and attests it. Then it waits for approval on the nuget environment, signs in to nuget.org with Trusted Publishing (GitHub OIDC exchanged for a one-hour key, no stored API key), pushes, and creates a GitHub Release with the changelog section and the packages.

  1. Add the section to CHANGELOG.md, with its date, and set <Version> in JsonPrettyPrinterPlus/JsonPrettyPrinterPlus.csproj.
  2. Merge through a pull request and wait for ci to be green on master.
  3. Tag the commit v<version> and push the tag. Tag only after green: tags are not force-pushed here, so a tag on a failing commit burns the number. That is how 2.1.0 and 3.0.0 became 2.1.1 and 3.0.1.
  4. Approve the deployment in the Actions run, then run verify-published.yml with the version: it waits for both nuget.org indexes, checks the repository signature and runs the consumers from nuget.org on Linux, macOS and Windows.
  5. Set PackageValidationBaselineVersion in the project file to the released version, so the next pack is checked against it.

Rules in one place

From AGENTS.md:

  • Output for well-formed input is the contract.
  • No net5+ APIs without an #if.
  • Trim and AOT annotations on anything reflective.
  • LF line endings.
  • The tag equals the version.
  • The golden recordings are never edited.
  • Write what was learned to ai-docs/ before finishing.
  • No AI attribution in commits, pull requests or files.

Clone this wiki locally