Json Schema Patterns in .NET - Reusing Common Types

In this series we are cataloging common patterns with JSON Schema and the .NET code generated by Corvus.JsonSchema.
It is especially useful for developers who want to move to a schema-first approach, but are unsure how that will map to their .NET code.
We are focusing on draft 2020-12 and the dotnet8.0 code generation, but very similar patterns apply for older versions of both .NET and draft 2019-09, draft 7, and even draft 6. We will highlight the key differences as we go.
If you have no experience of JSON Schema at all, I would recommend you read the getting started step-by-step documentation provided by the JSON Schema team.
Using common types by reference
In our previous example, we constrained all of our types to a string of length 1-256.
File: person.json
{
"title": "The person schema https://schema.org/Person",
"type": "object",
"required": [ "familyName", "givenName", "birthDate" ],
"properties": {
"familyName": {
"type": "string",
"minLength": 1,
"maxLength": 256
},
"givenName": {
"type": "string",
"minLength": 1,
"maxLength": 256
},
"otherNames": {
"type": "string",
"minLength": 1,
"maxLength": 256
},
"birthDate": {
"type": "string",
"format": "date"
},
"height": {
"type": "number",
"format": "double",
"minimum": 0.0,
"maximum": 3.0
}
}
}
We can simplify this by re-using a reference to a common schema with the $ref
keyword.
File: person-common-schema.json
{
"title": "The person schema https://schema.org/Person",
"type": "object",
"required": [ "familyName", "givenName", "birthDate" ],
"properties": {
"familyName": { "$ref": "#/$defs/constrainedString" },
"givenName": { "$ref": "#/$defs/constrainedString" },
"otherNames": { "$ref": "#/$defs/constrainedString" },
"birthDate": {
"type": "string",
"format": "date"
},
"height": {
"type": "number",
"format": "double",
"exclusiveMinimum": 0.0,
"maximum": 3.0
}
},
"$defs": {
"constrainedString": {
"type": "string",
"minLength": 1,
"maxLength": 256
}
}
}
Generate the code:
generatejsonschematypes --outputPath Model --rootNamespace JsonSchemaSample.Api person-common-schema.json
Generating: ConstrainedString
Generating: PersonCommonSchema
Generating: HeightEntity
Instead of the three types: GivenNameEntity
, FamilyNameEntity
, and OtherNamesEntity
, we have a single type: ConstrainedString
.
using JsonSchemaSample.Api;
using NodaTime;
var personCommonSchema = PersonCommonSchema.Create(
birthDate: new LocalDate(1820, 1, 17),
familyName: "Brontë",
givenName: "Anne",
// Invalid string length
otherNames: string.Empty,
height: 1.57);
// Custom types can be converted in the same way as the primitive types on which they are based.
PersonCommonSchema.ConstrainedString constrainedGivenName = personCommonSchema.GivenName;
PersonCommonSchema.ConstrainedString constrainedFamilyName = personCommonSchema.FamilyName;
string cgn = (string)constrainedGivenName;
string cfn = (string)constrainedFamilyName;
// Low-allocation comparisons are available in the usual way (as are all the other features of JsonString).
constrainedGivenName.Equals(constrainedFamilyName);
constrainedGivenName.EqualsString("Hello");
constrainedGivenName.EqualsUtf8Bytes("Anne"u8);