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
$refresolution 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.2.4</version>
</dependency>
-
Gradle (in your
build.gradlefile):
dependencies {
compile 'io.vertx:vertx-json-schema:4.2.4'
}
Concepts
Schema
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:
-
Draft201909SchemaParserfor Json Schema Draft 2019-09 -
Draft7SchemaParserfor Json Schema Draft 7 -
OpenAPI3SchemaParserfor 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
validateSyncandvalidateAsync -
Asynchronous: One or more branches of the validator tree requires an asynchronous validation. You must use
validateAsyncto validate your json. If you usevalidateSyncit 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);