generated from cotes2020/chirpy-starter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
49c9ef6
commit 71cc95a
Showing
2 changed files
with
95 additions
and
0 deletions.
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
--- | ||
title: "Dropping Project Support for Code Generation" | ||
date: 2024-02-11 09:00:00 +1200 | ||
tags: [json-schema, codegen] | ||
toc: true | ||
pin: false | ||
--- | ||
|
||
Some time ago, I released [my first attempt at code generation](/posts/exploring-codegen) 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. | ||
|
||
```json | ||
{ | ||
"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 more than mere auto-properties, it generates extra code in the setter to ensure that the model stays within the constraints expressed in the schema, even at runtime. | ||
|
||
This means that the setter for `foo` would also contain logic similar to | ||
|
||
```c# | ||
if (value < 0) | ||
throw new ArgumentException(nameof(value), "Value must be greater than 0"); | ||
|
||
_foo = value; | ||
``` | ||
|
||
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` or `decimal` or `integer`. | ||
|
||
As an example, consider this string: | ||
|
||
```json | ||
{ "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`. | ||
|
||
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](https://www.youtube.com/watch?v=aTcD-axJBac). | ||
{: .prompt-tip } | ||
|
||
## 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](https://github.com/mwadams), one of the primary contributors to _Corvus.JsonSchema_, as co-mentor on a [project proposal](https://github.com/json-schema-org/community/issues/614) 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.) |