From 71cc95ab8da76f328fc5555222f7089aa0d5a7fe Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Sun, 11 Feb 2024 16:12:10 +1300 Subject: [PATCH 1/3] add post about corvus.jsonschema --- .jekyll-metadata | Bin 22398 -> 24775 bytes _posts/2024/2024-02-11-dropping-codegen.md | 95 +++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 _posts/2024/2024-02-11-dropping-codegen.md diff --git a/.jekyll-metadata b/.jekyll-metadata index 137709b626e5b5e906dd57c8116a327439cbc0a6..b59c459792f92ab607f01a92c8cb8a583d7d448f 100644 GIT binary patch delta 185 zcmeyjj`8?GMs^mC>OfB=ugMq96(=`H3vIOOl;%ifk#ERIOGuw=s3p7kh4e!G&0Bp> za!$7L4FyYiX91;hy;YeV7FxP4tZnj7)S5jC2hRbyJG+3kovx(sh&bQ&Q7Y^Yn64*sK{s es@WYlJWH+Fc#~P>8_NA7btaz|mjOCPp9=sT);%o% delta 118 zcmV-+0Ez#{!2$lR0S5#Kdr(OtN|VtyA(Ma`60tgo8wlwGAAkvoJ7kki6CJbY8=)_g z%T)=J5e80^nn?|l*i{{q5e8ZtKmo`=0m(oC%0L0jKmp7^0nI=G&OiarKmpK`P)fa% YR8><5U<4n48@pidljk)ZvxrN|3C7?nCIA2c diff --git a/_posts/2024/2024-02-11-dropping-codegen.md b/_posts/2024/2024-02-11-dropping-codegen.md new file mode 100644 index 0000000..e913da3 --- /dev/null +++ b/_posts/2024/2024-02-11-dropping-codegen.md @@ -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.) From 197ba39ea6ffd2368810d5abce87e305d233e1c5 Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Mon, 12 Feb 2024 10:28:24 +1300 Subject: [PATCH 2/3] setters don't throw --- _posts/2024/2024-02-11-dropping-codegen.md | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/_posts/2024/2024-02-11-dropping-codegen.md b/_posts/2024/2024-02-11-dropping-codegen.md index e913da3..a4c5c3f 100644 --- a/_posts/2024/2024-02-11-dropping-codegen.md +++ b/_posts/2024/2024-02-11-dropping-codegen.md @@ -30,16 +30,7 @@ To show this, consider the following schema. } ``` -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; -``` +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. From 845ca4922ceb137425e409599cb3fabb3d9cbc37 Mon Sep 17 00:00:00 2001 From: Greg Dennis Date: Mon, 12 Feb 2024 10:32:16 +1300 Subject: [PATCH 3/3] add clarity that the property is strongly typed --- _posts/2024/2024-02-11-dropping-codegen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2024/2024-02-11-dropping-codegen.md b/_posts/2024/2024-02-11-dropping-codegen.md index a4c5c3f..26604e5 100644 --- a/_posts/2024/2024-02-11-dropping-codegen.md +++ b/_posts/2024/2024-02-11-dropping-codegen.md @@ -68,7 +68,7 @@ But the kicker is that everything simply references the original string. ## 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`. +_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!