Home Enhancing Deserialization with JSON Schema
Enhancing Deserialization with JSON Schema
Cancel

Enhancing Deserialization with JSON Schema

JsonSchema.Net includes a JSON converter implementation that provides JSON validation support during deserialization.

To enable this support, you’ll need to include the ValidatingJsonConverter in the serialization options and then annotate any types that need validation with the [JsonSchema()] attribute, pointing the the schema for that type.

Let’s walk through it.

More on JSON Schema support during deserialization can be found on the json-everything blog.

The validating converter described in this document requires AOT-incompatible reflection to operate, so it will not be usable in a Native AOT context.

Setting up the converter

Custom JSON converters are added via the JsonSerializationOptions.Converters property. Any converters in this collection will have priority over the default set of converters that ship with .Net. You can read more about custom converters in their documentation.

When preparing to deserialize your payload, create an options object and add the ValidatingJsonConverter from JsonSchema.Net:

1
2
3
4
var options = new JsonSerializationOptions
{
    Converters = { new ValidatingJsonConverter() }
};

Whenever you deserialize with these options, this converter will be queried to see if the type to deserialize is configured with a [JsonSchema()] attribute. If it is, then the payload will be validated against the schema prior to deserialization.

1
var myModel = JsonSerializer.Deserialize<MyModel>(jsonText, options);

Error reporting

If the data isn’t valid, then a JsonException will be thrown. The validation results will be in the .Data dictionary on the exception under the "validation" key. (You’ll need to cast it to EvaluationResults.)

Configuration

The validation can be configured using properties on the converter.

  • OutputFormat configures the JSON Schema output format to be used. By default, this value is Flag (the same as on EvaluationOptions).
  • RequireFormatValidation will validate the format keyword when set to true. By default format is an annotation.

Declaring a JSON Schema for a type

To declare a JSON Schema for any type, it needs to be decorated with the [JsonSchema()] attribute.

This attribute takes two parameters:

  • the type in which the schema is defined
  • the name of a public static property or field which returns a JsonSchema

For example, for MyModel

1
2
3
4
public class MyModel
{
    public string Foo { get; set; }
}

we need to define a schema. It can be in the same class or another class. For now, let’s assume we’re collecting our schemas in a single static class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static class Schemas
{
    public static JsonSchema MyModelSchema =
        new JsonSchemaBuilder()
            .Type(JsonSchemaType.Object)
            .Properties(
                (nameof(MyModel.Foo), new JsonSchemaBuilder()
                    .Type(JsonSchemaType.String)
                    .MinLength(10)
                    .MaxLength(50)
                )
            )
            .Required(nameof(MyModel.Foo))
            .AdditionalProperties(false);
}

MyModelSchema can be either a field, as shown, or a property, but it must be public and static.

In this example, we’ve declared that any JSON that represents MyModel must be an object that contains a single string property named Foo, which must be between 10 and 50 characters long, and no other properties are permitted.

The final step is to decorate MyModel with the [JsonSchema()] attribute.

1
2
3
4
5
[JsonSchema(typeof(Schemas), nameof(Schemas.MyModelSchema))]
public class MyModel
{
    public string Foo { get; set; }
}

That’s it.

Why not use System.ComponentModel.DataAnnotations to annotate and validate the model?

System.Text.Json doesn’t actually support these annotations.

The unit tests for the converter demonstrate several of these scenarios.

ASP.Net has separate code that can validate the model after it’s been deserialized, but that has several downsides.

Different error sources

Some errors are caught by the serializer, but others can only be caught by model validation.

If a property in the JSON doesn’t have the right type, the serializer will catch it because it can’t deserialize the value into the required type.

But the serializer can’t catch if a value is simply out of an acceptable range or if a required property is missing.

Having these two sources of errors can be confusing to the user/client and difficult to maintain for coders.

Using a JSON Schema to validate during deserialization ensures that all of the model validation occurs in one place.

Lackluster error reporting

When there are multiple errors, the deserializer or model validation is only going to report the first one it encounters.

With JSON Schema’s output formats, you can get all of the errors with a single deserialization attempt.

Contents