Spring Boot With Json Schema Validator#
What Is The Json Schema?#
JSON Schema
is a declarative language that allows you to annotate and validate JSON documents.JSON Schema
enables the confident and reliable use of the JSON data format.-
JSON Schema
can help us to:- Describes your existing data format(s).
- Provides clear human- and machine- readable documentation.
- Validates data which is useful for:
- Automated testing.
- Ensuring quality of client submitted data.
-
A
JSON Schema
document is itself a JSON document thatspecifies the rules for validating
another JSON document. It includes properties that define the schema's structure and validation rules, such as the data types allowed for each property, the minimum and maximum values allowed, and the pattern of characters allowed for string values. -
JSON Schema
can be used to validate JSON data against a specific schema, ensuring that the data conforms to the expected structure and constraints. It can be used to validate data in a variety of contexts, including APIs, databases, and data exchange formats.
Json Schema Versions#
-
JSON Schema
is a specification that defines a way to describe the structure and validation constraints for JSON (JavaScript Object Notation) documents. It is versioned like any other software specification, with each version introducing new features and changes. -
Previous versions
of JSON Schema includedraft-07
,draft-06
,draft-04
, anddraft-03
, each with its own set of features and changes. While these older versions are still in use in some contexts, they are gradually being replaced by the newerdraft-2020-12
version.
Json Schema Syntax#
- Base on the Json Schema Version that we are using, then we will have more or less syntax. Let's start with
draft-04
anddraft-07
because thedraft-04
is quite old but it is still using in many legacy system.draft-07
is a relatively minor update fromdraft-06
and it is fully backwards-compatible withdraft-06
.
Json Schema Draft-04#
- Below is the table of syntax that are supported in Json Schema Version
draft-04
.
Syntax | Description |
---|---|
$schema | Specifies the version of the JSON Schema specification being used. |
id | Defines a unique identifier for a schema. |
$id | Defines a URI-based identifier for a schema. |
type | Specifies the data type of a property. |
properties | Defines the set of properties that an object can have, along with their expected types. |
items | Defines the expected type of items in an array. |
required | Specifies the list of required properties for an object. |
minimum | Defines the minimum value allowed for a numeric property. |
maximum | Defines the maximum value allowed for a numeric property. |
exclusiveMinimum | Specifies whether the minimum value is included in the range of allowed values. |
exclusiveMaximum | Specifies whether the maximum value is included in the range of allowed values. |
minLength | Defines the minimum length of a string property. |
maxLength | Defines the maximum length of a string property. |
pattern | Defines a regular expression pattern that a string property must match. |
enum | Specifies a list of allowed values for a property. |
format | Specifies the expected format for a property, such as a date or time. |
additionalProperties | Specifies whether additional properties are allowed for an object. |
dependencies | Defines the dependencies between properties in an object. |
allOf, anyOf, oneOf, not | Define logical operators that can be used to combine multiple schemas. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
|
- With the example Json Schema above, we will have the valid json as below.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Json Schema Draft-07#
- Below is the table of syntaxes that are supported in Json Schema Version
draft-07
.
Syntax | Description |
---|---|
$schema | Specifies the version of the JSON Schema specification being used. |
$id | Defines a URI-based identifier for a schema. |
title | Defines a human-readable title for a schema. |
description | Provides a detailed description of a schema. |
default | Specifies a default value for a property. |
type | Specifies the data type of a property. |
enum | Specifies a list of allowed values for a property. |
const | Specifies a single allowed value for a property. |
multipleOf | Defines a number that a numeric property must be a multiple of. |
maximum | Defines the maximum value allowed for a numeric property. |
exclusiveMaximum | Specifies whether the maximum value is included in the range of allowed values. |
minimum | Defines the minimum value allowed for a numeric property. |
exclusiveMinimum | Specifies whether the minimum value is included in the range of allowed values. |
maxLength | Defines the maximum length of a string property. |
minLength | Defines the minimum length of a string property. |
pattern | Defines a regular expression pattern that a string property must match. |
items | Defines the expected type of items in an array. |
additionalItems | Specifies whether additional items are allowed in an array. |
maxItems | Defines the maximum number of items allowed in an array. |
minItems | Defines the minimum number of items allowed in an array. |
uniqueItems | Specifies whether all items in an array must be unique. |
contains | Specifies that an array must contain at least one element that matches a given schema. |
maxProperties | Defines the maximum number of properties allowed in an object. |
minProperties | Defines the minimum number of properties allowed in an object. |
required | Specifies the list of required properties for an object. |
properties | Defines the set of properties that an object can have, along with their expected types. |
patternProperties | Defines a set of properties that an object can have, where the property names match a regular expression pattern. |
additionalProperties | Specifies whether additional properties are allowed for an object. |
dependencies | Defines the dependencies between properties in an object. |
propertyNames | Defines a schema for the names of properties in an object. |
if, then, else | Define conditional schema keywords that allow different validation rules to be applied depending on whether a condition is true or false. |
allOf, anyOf, oneOf, not | Define logical operators that can be used to combine multiple schemas. |
$ref | Specifies a reference to another schema, allowing it to be reused within a schema. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
|
- Then below is the valid json for the example json schema above.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
- Below is the table that contains incompatible syntax between the
draft-04
anddraft-07
versions.
Syntax | Description | Compatibility |
---|---|---|
exclusiveMaximum and exclusiveMinimum | In draft-04, these keywords were optional and defaulted to false. In draft-07, they are required and default to false and true, respectively. | Incompatible |
$ref | In draft-04, $ref can reference any JSON Schema definition, including definitions defined inline. In draft-07, $ref can only reference definitions that are defined outside the current schema. | Incompatible |
additionalProperties and patternProperties | In draft-04, these keywords were not mutually exclusive, meaning that both could be used to define properties in an object. In draft-07, they are mutually exclusive, and if patternProperties is used, additionalProperties can only be used to define boolean values. | Incompatible |
type | In draft-04, type can be a string or an array of strings. In draft-07, type must be a string, and if multiple types are allowed, they must be listed in an array under the anyOf keyword. | Incompatible |
dependencies | In draft-04, dependencies can only define the dependencies between properties in an object. In draft-07, dependencies can also define schema dependencies, where the presence or absence of a property is dependent on whether another property validates against a given schema. | Incompatible |
maximum and minimum | In draft-04, maximum and minimum can be any numeric value. In draft-07, they must be finite numbers, and the use of NaN, Infinity, and -Infinity is prohibited. | Incompatible |
oneOf, anyOf, and allOf | In draft-04, these keywords do not support if/then/else clauses, and their behavior is not well-defined when used with other schema keywords like additionalProperties. In draft-07, they have well-defined behavior and can be used with if/then/else clauses. | Incompatible |
Json Schema Validation With Spring Boot#
- Implementations of Json Schema are very popular and supports many programming languages, you can view this page for more details.
- In Java, there are also many Json Schema Implementations and in this example we will use the library
everit-json-schema
because this library is the most common with highest users and rating and it also supported 3 versions: draft-04, draft-06 and draft-07.
Prepare#
- Firstly, let's take a look about the example that we are going to do. So let's images that we have a spring boot application service that is used for storing customer information.
- The customer information is actually a json with contains some information like
identity
,orders
anditems
and there some constraints such as theidentity
field is only mandatory for theageRange
which isADULT
, in case theageRange
isCHILDREN
or there is no fieldageRange
it is the optional etc.... Let's see the example customer json as below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
- The Json Schema and Customer Data will be stored in a collection in the MongoDB in which when we are going to create a customer then the json schema will be loaded from the MongoDB to check the input customer json is valid or not. Let's see the diagram below.
- Before deep dive into the example, we need to prepare the MongoDB. We can use the docker to create it quickly, you can view Docker With Databases for more details.
Dependencies#
- Now, let's add some dependencies as below into our
pom.xml
forSpring MongoDB
andeverit-json-schema
.
pom.xml | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
Config#
- Let's create a package
config
and add the configuration classValidationConfig
as below to set up validation for MongoDB operations.
ValidationConfig.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
- In this configuration class, we will create a bean
validator
by the new instance ofLocalValidatorFactoryBean
in which theLocalValidatorFactoryBean
provides an implementation ofjavax.validation.Validator
. -
Then the bean
validator
will be used as a parameter to create a beanvalidatingMongoEventListener
by a new instance ofValidatingMongoEventListener
, this instance will listen MongoDB events and validates entities before they are saved to the database by usingjavax.validation.Validator
in the input parameter. -
Next, in the main class
@EnableMongoRepositories
we will use the annotation@EnableMongoRepositories
to enable MongoDB repositories in the application. When used, Spring will look for interfaces that extend theMongoRepository
interface and create instances of them automatically.
JsonSchemaValidatorApplication.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Model#
-
Next, let's create a package model and create model classes as below:
Customer
: This model will be mapped with the collectioncustomers
in MongoDB.DatabaseSequence
: This model will be mapped with the collectiondatabase_sequence
in MongoDB.JsonSchemaValidator
: This model will be mapped with the collection# json_schemas
in MongoDBJsonValidationResponse
: This is just a simple DTO which is used for the response of Get Json Schema Validation api.
-
Firstly, let's create
Customer
model as below.
Customer.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
- As you can see, in this class we will use the annotation
@Document
to map this class to thecustomers
MongoDB collection. -
In this class, the field data will contain all the customer details.
-
Next, we will continue to create the
DatabaseSequence
class. This class will be mapped withdatabase_sequence
collection, this collection is used for generating sequence number which is used for fieldversion
inJsonSchemaValidator
class.
DatabaseSequence.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
- Next, We will create the
JsonSchemaValidator
class. This class will be mapped withjson_schemas
collection, this collection is used for storing json schemas which will be used for validating the customer json before saving into the MongoDB.
JsonSchemaValidator.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
- Finally, we just need to create a simple
JsonValidationResponse
as below.
JsonValidationResponse.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Repository#
- Now, we will continue to create the package
repository
and add 2 repositories classesCustomerRepository
andJsonSchemaRepository
as below.
CustomerRepository.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
- The
CustomerRepository
interface extendsMongoRepository
, which provides basic CRUD operations for theCustomer
class, such assave
,findAll
,findById
, anddeleteById
.
JsonSchemaRepository | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
- Likewise
JsonSchemaRepository
interface extendsMongoRepository
, which provides basic CRUD operations for theJsonSchemaValidator
class and in repository we also need to add some custom queries for finding Json SchemaValidator by name and by name with order by version.
Service#
-
So let's create 3 services and 1 component class as below:
- SequenceGeneratorService: used for generate sequence version number.
- CustomerService: used for creating and getting customer data.
- JsonSchemaValidatorService: userd for validating json data, creating and getting json schemas.
- CustomDateTimeValidator: This component is used for overriding the default DateTimeFormatValidator when building
SchemaLoader
inJsonSchemaValidatorService
.
-
Firstly, let's create the
SequenceGeneratorService
class as below.
SequenceGeneratorService.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
-
In this service we will use the
mongoOperations
to query theDatabaseSequence
record inside thedatabase_sequence
collection withid
is the input seqName. If the record is existed then we will update the value by increasing current value with 1 else we will create a new record with value is 1. -
Secondly, let's create the
CustomDateTimeValidator
component class as below.
CustomDateTimeValidator.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
|
-
Actually, we are going to create a custom
DateTimeFormatValidator
which will be used to replace the defaultDateTimeFormatValidator
of thiseverit-json-schema
. So we have to implement the interfaceFormatValidator
to create a new customDateTimeFormatValidator
. In this component, we will override 2 methodsformatName
andvalidate
.- In which, the
formatName
is used for the name of theFormatValidator
that we want to set, if the name is already existed then later when we configure theSchemaLoader
we will override it, if the name is new and has not existed as in the library then it will be the new custom format when we add it to theSchemaLoader
and we can use it in the json schema. - In the method
validate
we will receive the string value which we are expecting as the datetime and then we will use the customDateTimeFormatterBuilder
to verify this string value. The datetime value should have the formats as in the example below:- 2023-03-28T14:11:44.849+0700
- 2023-03-28T14:11:44.849Z
- 2023-03-28T14:11:44
- In which, the
-
Next, let's create the
JsonSchemaValidatorService
as below.
JsonSchemaValidatorService.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
|
- In this service class we will have 3 main methods,
createJsonSchemaValidator
,getJsonSchemaValidatorByName
andvalidateJsonData
. - In the method
createJsonSchemaValidator
firstly we will validate the input body by using another json schem which is put in the resources as below.
JsonSchemaStructureValidator.json | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
|
-
This json schema will validate the input json scheme in the request body to make sure the request body and the structure of in json schema validation is correct.
-
Then we will get the latest json schema version base on the schema name. If the schema existed in the MongoDB, then we will set the status
inactive
for it and we will create a new record with newer active version. If the schema isn't existed before then we will create a new one. - In the method
getJsonSchemaValidatorByName
, we just simply query all the json schemas by schema name. -
Finally in the method
validateJsonData
, we will get the latest json schema version, if the json schema doesn't exist, then we will return validation failed. If it exists, then we will create an instance ofSchemaLoader
by usingSchemaLoader.builder()
- Then we will set the
customDateTimeValidator
to theSchemaLoader
that we created before. - Then The
schemaJson(new JSONObject(validationSchema))
method loads the JSON schema from the MongoDB that we get in the step before. - The we use the
enableOverrideOfBuiltInFormatValidators()
method enables the override of the built-in format validators that come with the library. - Finally, the
build()
method builds theSchemaLoader
instance.
- Then we will set the
-
Next we will use
load().build();
to load the schema and builds it. -
Finally we use
validate(new JSONObject(target));
to validate the JSON objecttarget
against the JSON schema. If the validation fails, aValidationException
is thrown. -
Next, we will continue to create the
CustomerService
class as below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
- In this service class we will have 2 methods,
createCustomer
andgetCustomers
. In which:- In the method
createCustomer
, we will use theJsonSchemaValidatorService
to validate the input json, if the json is valid then we will create the customer else we will thrown an exception. - Next, in the method
getCustomers
, we just simply get all the created customers in the MongoDB.
- In the method
Controller#
- Next we create 2 controllers
CustomerController
andJsonSchemaValidatorController
respectively as below.
CustomerController.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
- In this controller, we simply create 2 apis for creating customer and getting all the customers in the MongoDB.
JsonSchemaValidatorController.java | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
- In this controller, we will create 3 apis for creating json schema, getting json schema by name and validate json with json schema. They are 3 apis for 3 main methods in the
JsonSchemaValidatorService
.
Testing#
- Okay let's take an example json schema as below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
|
- In this json schema we will validate the customer json to check it is valid or not. The valid customer json is showed as below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
-
In the customer json, it contains some information like
identity
,orders
anditems
and there some constraints such as theidentity
field is only mandatory for theageRange
which isADULT
, in case theageRange
isCHILDREN
or there is no fieldageRange
it is the optional. Then for the listorders
, it can be empty because the customer haven't had any order yet. Then if the customer has an order, then all the information in the order must be required, included the listitems
. -
Now, Firstly we will use the postman to add the json schema validator as below. Then we will receive the newest version with active status.
- Then we can use the api get json schema by name to get all the json schema version to make sure the json schema is saved successfully into the MongoDB.
- Finally, now, we can try to validate the customer json by using api
validateCustomerJson
. So if we put the valid json we will see the response with fieldvalidJson
astrue
.
- We try to set the field
identity
asnull
then you will see thevalidJson
asfalse
because theageRange
isADULT
and it requires the fieldidentity
.
- But if we change the
ageRange
toCHILDREN
, then you will see the customer json is valid again.
- Next, we will try to create the customer with valid json, then you will see the successful result as below.
- If we make the customer json invalid then we will got the internal error.
- Then to make sure the customer data is created successfully we can use the api get customers to check.