JSON is arguably one of the most used data formats on the planet, likely due to its flexibility and human readability. We can also recognize that its two greatest strengths can also be the formatโs weakness. Being human-readable gives developers the confidence to make manual changes, likely making errors along the way. The flexibility of parsers tends to let these structural errors exist in production settings longer than they should.
Luckily for .NET developers, we have mechanisms to programmatically check the validity of JSON to ensure some level of correctness. Weโll see how to read JSON and check it against its schema definition.
Whatโs JSON Schema?
JSON schema attempts to help define a clear expectation for JSON objects. The schema describes the format of JSON in a clear human-readable and machine-readable format. Users of JSON Schema can use it to perform structural validation ideal for validating incoming client requests and automating integration tests by generating test input.
Most modern editors support JSON schema by placing a $schema
property at the start of a JSON model. Letโs see an example of JSON schema.
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Product",
"description": "A product from Acme's catalog",
"type": "object",
"properties": {
"id": {
"description": "The unique identifier for a product",
"type": "integer"
},
"name": {
"description": "Name of the product",
"type": "string"
},
"price": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"uniqueItems": true
}
},
"required": ["id", "name", "price"]
}
Some notable features of JSON Schema include:
- Descriptions of each property in a JSON object
- Type expectations for each property
- Value expectations for each property
- Requiring fields
- JSON Schemas can also be validated using JSON Schema
Weโll be using this schema later in our example .NET application.
Validating JSON using .NET and NJsonSchema
Before we get started, weโll need an empty console application with the NuGet package of NJsonSchema installed. While other packages are available for JSON schema validation, Iโve found this to be the most straightforward to use.
> dotnet add NJsonSchema
Our goal is to read the $schema
property from an existing JSON file and then output the validation results to the console. Weโll also be using Spectre.Console for nice display output, but simple Console.WriteLine
would work just as well.
Our sample will have three different JSON documents: valid.json
, empty.json
, and schemaless.json
. The variants of JSON objects will help us see different results. Weโre reading the schema.json
file from disk, but we could just as quickly read it from a remote URL, which is usually the case.
// empty.json
{
"$schema": "schema.json"
}
// schemaless.json
{
"wild": "west"
}
// valid.json
{
"$schema": "schema.json",
"id": 1,
"name": "",
"price": 149.00
}
Now that we have all three JSON files and our schema, we can write some code.
using System.IO;
using System.Linq;
using Newtonsoft.Json.Linq;
using NJsonSchema;
using Spectre.Console;
var files = new[] { "valid.json", "empty.json", "schemaless.json"};
var table = new Table().RoundedBorder()
.AddColumn("๐ file name")
.AddColumn("๐จ errors");
foreach (var file in files)
{
var text = await File.ReadAllTextAsync(file);
var json = JToken.Parse(text);
// use the schema on the json model
var jsonSchema = json["$schema"]?.ToString();
var schema = jsonSchema switch {
{Length: > 0} when jsonSchema.StartsWith("http") =>
await JsonSchema.FromUrlAsync(jsonSchema),
{Length: > 0} =>
await JsonSchema.FromFileAsync(jsonSchema),
_ => null
};
if (schema is null)
{
table.AddRow(file, "[purple]unavailable $schema[/]");
continue;
}
var errors = schema.Validate(json);
var results = errors.Any()
? $"โฃ {errors.Count} total errors\n" +
string.Join("", errors
.Select(e => $" โฃ [red]{e}[/] at " +
$"[yellow]{e.LineNumber}:{e.LinePosition}[/]\n"))
: "[green]โ[/] [lime]looks good![/]";
table.AddRow(file, results);
}
AnsiConsole.Render(table);
As we can see, we parse our JSON files and load the schema as we go through each file. Loading schema per file gives us the ultimate flexibility to validate each file independently. In situations where all JSON models are identical, I would recommend loading the JSON schema once.
Running our console application, we get the expected results. (The results look much nicer locally).
โญโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ ๐ file name โ ๐จ errors โ
โโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ valid.json โ โ looks good! โ
โ empty.json โ โฃ 3 total errors โ
โ โ โฃ PropertyRequired: #/id at 1:1 โ
โ โ โฃ PropertyRequired: #/name at 1:1 โ
โ โ โฃ PropertyRequired: #/price at 1:1 โ
โ โ โ
โ schemaless.json โ unavailable $schema โ
โฐโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
Awesome!
Using JsonSchema.NET To Validate JSON
Recently, Iโve also found out how to use JsonSchema.NET, which has support for System.Text.Json
elements. If youโre already using System.Text.Json
, which you likely are if youโre on any version of .NET 5+ or higher, youโll likely want to use this package. An important note about JsonSchema.NET, is it only has support for drafts 6,8,2019-09, and 2020-12. The above schema must be modified, especially the exclusiveMinimum
field, which is now an number.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Product",
"description": "A product from Acme's catalog",
"type": "object",
"properties": {
"id": {
"description": "The unique identifier for a product",
"type": "integer"
},
"name": {
"description": "Name of the product",
"type": "string"
},
"price": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": 0
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"uniqueItems": true
}
},
"required": ["id", "name", "price"]
}
Letโs use the above schema, with the code found below.
using System;
using System.IO;
using System.Text.Json;
using Json.Schema;
var schema = JsonSchema.FromFile("schema.json");
var file = File.OpenRead("data.json");
var json = await JsonDocument.ParseAsync(file);
var result = schema.Validate(json.RootElement);
var validity = result.IsValid ? "Valid" : "Invalid";
Console.WriteLine($"Json is {validity}");
With a few lines of code we are now validating JSON documents. Yay! You can also choose the level of validation youโd like by passing a ValidationOptions
parameter to Validate
.
var result = schema.Validate(json.RootElement, new ValidationOptions {
RequireFormatValidation = true,
ValidateAs = Draft.Draft7,
OutputFormat = OutputFormat.Verbose
});
Conclusion
JSON is everywhere, and the ability to validate an otherwise flexible data format is excellent. JSON Schema allows us to enforce a structure that we can use to give users feedback. Many editors already support the $schema
property, giving us real-time validation issues. Using something like NJsonSchema allows us to use an existing schema to add a layer of validation that can reduce problems before they get out of hand. If we donโt have a schema, then thatโs no problem, as the JSON schema format is easier to write and maintain.
I hope you enjoyed this blog post. If you did, please share it with your friends and coworkers.