At endjin, we maintain Corvus.JsonSchema, an open source high-performance library for serialization and validation of JSON using JSON Schema.
Today we're introducing something we've been working on for a while: the V5 engine. It is a new code generator and runtime library that sits alongside our existing V4 engine, and represents a fundamentally different set of design trade-offs.
In this series, we'll walk through what V5 brings, why it exists, and how to get started.
The problem we're solving
If you work with JSON in .NET, you've probably noticed a few gaps in the built-in tooling.
System.Text.Json provides excellent low-level readers and writers, and a high-performance serialization framework for code-first projects. But it has no built-in schema validation, no query language support, and its mutable document model (JsonNode) allocates per node with no control over memory lifetime.
Meanwhile, JSON itself has become the lingua franca of APIs, configuration, message passing, and data exchange. You need to parse it, validate it, query it, transform it, patch it, and write it back out. This often happens in a high-throughput request/response pipeline where every allocation matters.
That's what Corvus.Text.Json is for.
The V5 engine doesn't replace V4. It offers a different set of trade-offs.
| Aspect |
V4 (Corvus.Json) |
V5 (Corvus.Text.Json) |
| Mutation model |
Immutable-functional - With*() returns a new instance |
Mutable builder - Set*() mutates in-place |
| Memory |
JsonElement backed by JsonDocument |
ParsedJsonDocument<T> backed by ArrayPool<byte> |
| Safety |
Stale references impossible (immutable) |
Version-tracked stale reference detection |
| Performance |
Good |
Considerably faster, lower allocation |
| Target frameworks |
net8.0, net10.0, netstandard2.0 |
net9.0, net10.0, netstandard2.0, netstandard2.1 |
V4 is the right choice when you want the guarantees of an immutable document model - no aliasing bugs, no stale references, and the ability to share values freely across threads.
V5 is the right choice when you're building request/response pipelines, message processing, or other scenarios where you create a document, use it briefly, and dispose it. The builder pattern pools memory across operations, dramatically reducing GC pressure.
Both engines ship in the same CLI tool (corvusjson) and share the same JSON Schema analysis engine. You choose which to use with the --engine flag.
What V5 brings
Pooled-memory parsing
ParsedJsonDocument<T> uses ArrayPool<byte> for its backing memory. It adds just 136 bytes of GC pressure per document, regardless of size, compared with 1,528 bytes for a comparable JsonNode. That's 92% less memory.
using var doc = ParsedJsonDocument<Person>.Parse(
"""{"name":"Alice","age":30}""");
Person person = doc.RootElement;
string name = (string)person.Name; // "Alice"
Mutable documents with JsonWorkspace
JsonDocumentBuilder<T> and JsonWorkspace provide a pooled builder pattern for creating and modifying JSON in place, with version tracking to catch stale element references at runtime.
using JsonWorkspace workspace = JsonWorkspace.Create();
using var builder = person.CreateBuilder(workspace);
Person.Mutable root = builder.RootElement;
root.SetAge(31);
Console.WriteLine(root.ToString()); // {"name":"Alice","age":31}
Source-generated types
The [JsonSchemaTypeGenerator] attribute triggers a Roslyn incremental source generator that produces strongly-typed C# models from JSON Schema at build time. You get full IntelliSense as you type.
using Corvus.Text.Json;
namespace MyApp.Models;
[JsonSchemaTypeGenerator("Schemas/person.json")]
public readonly partial struct Person;
Schema validation - over 10× faster
Full JSON Schema validation for drafts 4, 6, 7, 2019-09, and 2020-12 (including OpenAPI 3.0 and 3.1). Over 10× faster than other .NET JSON Schema validators, with detailed hierarchical diagnostics when you need them.
bool valid = person.EvaluateSchema(); // true
V5 includes complete implementations of three JSON query/transformation languages, all with 100% conformance against their official test suites:
- JSONata - a Turing-complete functional query and transformation language. On average 2× faster than Jsonata.Net.Native with 90–100% less memory allocation across 32 benchmarks.
- JMESPath - a standard query language used by AWS CLI and Azure CLI. On average 28× faster than JmesPath.Net with zero allocation across 21 benchmarks.
- JsonLogic - a safe, side-effect-free rule engine for evaluating business rules stored as JSON. On average 3× faster than JsonEverything with zero allocation across 19 benchmarks.
All three support interpreted runtime evaluation, build-time source generation, and CLI code generation.
YAML 1.2 to JSON
YAML is a superset of JSON, and in practice the two are interchangeable in many configuration and data-exchange scenarios. Kubernetes manifests, OpenAPI specifications, GitHub Actions workflows, Azure DevOps pipelines, Helm charts, Docker Compose files - a huge amount of the infrastructure-as-code ecosystem uses YAML as its primary format. If you're processing these in .NET, you eventually need to convert YAML to JSON so you can validate, query, or transform it with standard JSON tooling.
V5 includes a zero-allocation ref struct YAML 1.2 tokenizer that converts directly to a ParsedJsonDocument<T> or a System.Text.Json.JsonDocument with 100% yaml-test-suite conformance. There is no intermediate object model and no per-node allocations.
JSON Patch, JSON Pointer, and extended types
- JSON Patch - RFC 6902 with a fluent
PatchBuilder and pooled-memory operations
- JSON Pointer - RFC 6901 zero-allocation path resolution with source location mapping
- BigNumber - arbitrary-precision decimal arithmetic without floating-point conversion
- NodaTime - first-class support for
LocalDate, OffsetDateTime, Period, and other temporal types
At a glance: System.Text.Json vs Corvus.Text.Json
| Feature |
System.Text.Json |
Corvus.Text.Json |
| Read-only parsing |
JsonDocument (pooled) |
ParsedJsonDocument<T> (pooled, generic) |
| Mutable documents |
JsonNode (allocates per node) |
Builder pattern on pooled memory |
| Schema validation |
None built-in |
Draft 4/6/7/2019-09/2020-12, detailed diagnostics |
| Code generation |
Serialization to/from POCOs |
Strongly-typed entities from JSON Schema |
| Date/Time |
DateTime, DateTimeOffset |
All .NET types plus NodaTime |
| Numeric precision |
decimal (28 digits) |
BigNumber (arbitrary precision), Int128, Half |
| Query languages |
None |
JSONata, JMESPath, JsonLogic |
| YAML support |
None |
YAML 1.2 with zero-allocation tokenizer |
Getting started
# Core library
dotnet add package Corvus.Text.Json
# Source generator
dotnet add package Corvus.Text.Json.SourceGenerator
# CLI code generator
dotnet tool install --global Corvus.Json.Cli
What's coming in this series
We'll start with the developer experience: how the source generator and CLI tool turn a JSON Schema into a strongly-typed C# struct with full IntelliSense, and how the standalone evaluator gives you annotation collection for schema-driven tooling like form generators.
Then we'll dig into the runtime. We'll look at how ParsedJsonDocument<T> achieves 136 bytes per document with pooled-memory parsing, how the mutable JsonDocumentBuilder trades V4's immutable safety for in-place mutation with version tracking, and how schema validation runs over 10× faster across all major drafts.
From there, we'll move into the three query and transformation languages. JSONata handles general-purpose JSON transformation, JMESPath provides the concise extraction queries used by AWS and Azure CLIs, and JsonLogic offers safe, side-effect-free business rules you can store in a database and evaluate without code execution.
We'll cover YAML 1.2 conversion with a zero-allocation tokenizer that achieves 100% yaml-test-suite conformance, JSON Patch for RFC 6902 document mutation with a fluent builder, JSON Pointer for zero-allocation path resolution, and V5's extended type system with UTF-8 URI/IRI parsing, arbitrary-precision numerics, and first-class NodaTime integration.
Finally, we'll wrap up with the migration path from V4 to V5, the 10 production Roslyn analyzers that ship with the library, and how to get started.