Some time ago, I released my first attempt at code generation from JSON Schemas. However, I’ve decided to deprecate the library in favor of Corvus.JsonSchema.
When I created JsonSchema.Net.CodeGeneration, I knew about Corvus.JsonSchema, but I thought it was merely an alternative validator. I didn’t truly understand its approach to supporting JSON Schema in .Net.
Today we’re going to take a look at this seeming competitor to see why it actually isn’t one.
What is Corvus.JsonSchema?
Corvus.JsonSchema is a JSON Schema code generator that bakes validation logic directly into the model.
To show this, consider the following schema.
1
2
3
4
5
6
7
8
9
{
"type": "object",
"properties": {
"foo": {
"type": "integer",
"minimum": 0
}
}
}
As one would expect, the library would generate a class with a single property: int Foo
. But it also generates an .IsValid()
method that contains all of the validation logic. So if you set model.Foo = -1
, the .IsValid()
method will return false.
However Corvus.JsonSchema has another trick up its sleeve. But before we get into that, it will help to have some understanding of how System.Text.Json’s JsonElement
works.
A quick review of JsonElement
Under the hood, JsonElement
captures the portion of the parsed JSON text by using spans. This has a number of follow-on benefits:
- By avoiding substringing, there are no additional heap allocations.
JsonElement
can be a struct, which further avoids allocations, because it only maintains references to existing memory.- By holding onto the original text, the value can be interpreted different ways. For example, numbers could be read as
double
ordecimal
orinteger
.
As an example, consider this string:
1
{ "foo": 42, "bar": [ "a string", false ] }
Five different JsonElement
s would be created:
- top-level object
- number value under
foo
- array value under
bar
- first element of
bar
array - second element of
bar
array
But the kicker is that everything simply references the original string.
Value | Backing span |
---|---|
top-level object | start: 0, length: 44 |
number value under foo | start: 9, length: 2 |
array value under bar | start: 20, length: 21 |
first element of bar array | start: 22, length: 10 |
second element of bar array | start: 34, length: 5 |
Back to the validator
Corvus.JsonSchema builds on this “backing data” pattern that JsonElement
establishes. Instead of creating a backing field that is the same type that the property exposes, which is the traditional approach for backing fields, the generated code will use a JsonElement
for the backing field while the property is still strongly typed.
This means that a model generated by the library can usually be deserialized without any extra allocations, resulting in very high performance!
For a much better explanation of what’s going on inside the package than what I can provide, I recommend you watch their showcase video.
Keep moving forward
Ever since I saw that video, I’ve lamented the fact that it’s only available as a dotnet
tool. I’ve always envisioned this functionality as a Roslyn source generator.
To that end, I’ve paired with Matthew Adams, one of the primary contributors to Corvus.JsonSchema, as co-mentor on a project proposal for JSON Schema’s submission to Google’s Summer of Code. This project aims to wrap the existing library in an incremental source generator that uses JSON Schema files within a .Net project to automatically generate models at compile time.
This is a great opportunity to learn about incremental source generators in .Net and build your open source portfolio. If this sounds like a fun project, please make your interest known by commenting on the proposal issue linked above.
(Even if it’s not accepted by GSoc, we’re probably going to do it anyway.)
If you like the work I put out, and would like to help ensure that I keep it up, please consider becoming a sponsor!