Vert.x Json Schema
Vert.x Json Schema provides an extendable and asynchronous implementation for Json Schema specification. You can use Json Schemas to validate every json structure. This module provides:
-
Implementation of Json Schema draft2019-09
-
Implementation of Json Schema draft-7
-
Implementation of OpenAPI 3 dialect.
-
Non blocking
$ref
resolution and caching -
Lookup into the schema cache using
JsonPointer
-
Synchronous and asynchronous validation
-
Ability to extend the validation tree adding new keywords and new format predicates
-
DSL to build schemas programmatically
Using Vert.x Json Schema
To use Vert.x Json Schema, add the following dependency to the dependencies section of your build descriptor:
-
Maven (in your
pom.xml
):
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-json-schema</artifactId>
<version>4.1.8</version>
</dependency>
-
Gradle (in your
build.gradle
file):
dependencies {
compile 'io.vertx:vertx-json-schema:4.1.8'
}
Concepts
SchemaParser & SchemaRouter
The SchemaParser
is the component that parses the schemas from Json data structures to Schema
instances.
The SchemaRouter
is the component able to cache parsed schemas and resolve $ref
.
Every time a new $ref
is solved or a SchemaParser
parses a new schema, the new schema will be cached inside the corresponding SchemaRouter
.
The SchemaParser
can be extended to support custom keywords and formats.
The available SchemaParser
are:
-
Draft201909SchemaParser
for Json Schema Draft 2019-09 -
Draft7SchemaParser
for Json Schema Draft 7 -
OpenAPI3SchemaParser
for OpenAPI 3 dialect
Parse a schema
To parse a schema you first need a schema router and a schema parser matching your schema dialect. For example to instantiate a draft 2019-09 schema parser:
SchemaRouter schemaRouter = SchemaRouter.create(vertx, new SchemaRouterOptions());
SchemaParser schemaParser = SchemaParser.createDraft201909SchemaParser(schemaRouter);
You can reuse SchemaRouter
instance for different SchemaParser
and you can parse different Schema
with same SchemaParser
.
Now you can parse the schema:
Schema schema = parser.parse(object, schemaPointer);
When you parse a schema you must provide the schema pointer, a pointer that identifies the location of the schema.
If you don’t have any schema pointer SchemaParser
will generate one for you:
Schema schema = parser.parse(object);
schema.getScope(); // Get generated scope of schema (schema pointer)
Important
|
Remember that the schema pointer is required to reference this schema later using Json Schema |
Validate
A schema could have two states:
-
Synchronous: The validators tree can provide a synchronous validation. You can validate your json both using
validateSync
andvalidateAsync
-
Asynchronous: One or more branches of the validator tree requires an asynchronous validation. You must use
validateAsync
to validate your json. If you usevalidateSync
it will throw aNoSyncValidationException
To validate a schema in an asynchronous state:
schema.validateAsync(json).onComplete(ar -> {
if (ar.succeeded()) {
// Validation succeeded
} else {
// Validation failed
ar.cause(); // Contains ValidationException
}
});
To validate a schema in a synchronous state:
try {
schema.validateSync(json);
// Successful validation
} catch (ValidationException e) {
// Failed validation
} catch (NoSyncValidationException e) {
// Cannot validate synchronously. You must validate using validateAsync
}
To check the schema state you can use method isSync
.
The schema can mutate the state in time, e.g. if you have a schema that is asynchronous because of a $ref
,
after the first validation the external schema is cached and the schema will switch to synchronous state.
Note
|
If you use |
Adding custom formats
You can add custom formats to use with validation keyword format
before parsing the schemas:
parser.withStringFormatValidator("firstUppercase", str -> Character.isUpperCase(str.charAt(0)));
JsonObject mySchema = new JsonObject().put("format", "firstUppercase");
Schema schema = parser.parse(mySchema);
Adding custom keywords
For every new keyword type you want to provide, you must implement ValidatorFactory
and provide an instance to SchemaParser
using withValidatorFactory
.
When parsing happens, the SchemaParser
calls canConsumeSchema
for each registered factory.
If the factory can consume the schema, then the method createValidator
is called. This method returns an instance of Validator
, that represents the object that will perform the validation.
If something goes wrong during Validator
creation, a SchemaException
should be thrown
You can add custom keywords of three types:
-
Keywords that always validate the input synchronously
-
Keywords that always validate the input asynchronously
-
Keywords with mutable state
Synchronous keywords
Synchronous validators must implement the interface SyncValidator
.
In the example below I add a keyword that checks if the number of properties in a json object is a multiple of a provided number:
`link:../../apidocs/examples/PropertiesMultipleOfValidator.html[PropertiesMultipleOfValidator]`
After we defined the keyword validator we can define the factory:
`link:../../apidocs/examples/PropertiesMultipleOfValidatorFactory.html[PropertiesMultipleOfValidatorFactory]`
Now we can mount the new validator factory:
parser.withValidatorFactory(new PropertiesMultipleOfValidatorFactory());
JsonObject mySchema = new JsonObject().put("propertiesMultipleOf", 2);
Schema schema = parser.parse(mySchema);
Asynchronous keywords
Asynchronous validators must implement the interface AsyncValidator
.
In this example I add a keyword that retrieves from the Vert.x Event bus an enum of values:
`link:../../apidocs/examples/AsyncEnumValidator.html[AsyncEnumValidator]`
After we defined the keyword validator we can define the factory:
`link:../../apidocs/examples/AsyncEnumValidatorFactory.html[AsyncEnumValidatorFactory]`
Now we can mount the new validator factory:
parser.withValidatorFactory(new AsyncEnumValidatorFactory(vertx));
JsonObject mySchema = new JsonObject().put("asyncEnum", "enums.myapplication");
Schema schema = parser.parse(mySchema);
Building your schemas from code
If you want to build schemas from code, you can use the included DSL. Only Draft-7 is supported for this feature.
Creating the schema
Inside Schemas
there are static methods to create the schema:
SchemaBuilder intSchemaBuilder = intSchema();
SchemaBuilder objectSchemaBuilder = objectSchema();
Using the keywords
For every schema you can add keywords built with Keywords
methods,
depending on the type of the schema:
stringSchema()
.with(format(StringFormat.DATETIME));
arraySchema()
.with(maxItems(10));
schema() // Generic schema that accepts both arrays and integers
.with(type(SchemaType.ARRAY, SchemaType.INT));
Defining the schema structure
Depending on the schema you create, you can define a structure.
To create an object schema with some properties schemas and additional properties schema:
objectSchema()
.requiredProperty("name", stringSchema())
.requiredProperty("age", intSchema())
.additionalProperties(stringSchema());
To create an array schema:
arraySchema()
.items(stringSchema());
To create a tuple schema:
tupleSchema()
.item(stringSchema()) // First item
.item(intSchema()) // Second item
.item(booleanSchema()); // Third item
$ref
and aliases
To add a $ref
schema you can use the Schemas.ref
method.
To assign an $id
keyword to a schema, use id
You can also refer to schemas defined with this dsl using aliases. You can use alias
to assign an alias to
a schema. Then you can refer to a schema with an alias using Schemas.refToAlias
:
intSchema()
.alias("myInt");
objectSchema()
.requiredProperty("anInteger", refToAlias("myInt"));
Using the schema
After you defined the schema, you can call build
to parse and use the schema:
Schema schema = objectSchema()
.requiredProperty("name", stringSchema())
.requiredProperty("age", intSchema())
.additionalProperties(stringSchema())
.build(parser);