- When to use this library?
- Maven installation
- Quickstart
- Draft 4, Draft 6 or Draft 7?
- Investigating failures
- ValidationListeners - Tracking the validation process
- Early failure mode
- Lenient mode
- Default values
- RegExp implementations
- readOnly and writeOnly context
- Format validators
- $ref resolution
- Excluding dependencies
- Javadoc
This project is an implementation of the JSON Schema Draft v4, Draft v6 and Draft v7 specifications. It uses the org.json API (created by Douglas Crockford) for representing JSON data.
Lets assume that you already know what JSON Schema is, and you want to utilize it in a Java application to validate JSON data. But - as you may have already discovered - there is also an other Java implementation of the JSON Schema specification. So here are some advices about which one to use:
- if you use Jackson to handle JSON in Java code, then java-json-tools/json-schema-validator is obviously a better choice, since it uses Jackson
- if you want to use the org.json API then this library is the better choice
- if you need JSON Schema Draft 6 / 7 support, then you need this library.
- if you want to use anything else for handling JSON (like GSON or javax.json), then you are in a little trouble, since currently there is no schema validation library backed by these libraries. It means that you will have to parse the JSON twice: once for the schema validator, and once for your own processing. In a case like that, this library is probably still a better choice, since it seems to be twice faster than the Jackson-based java-json-tools library.
Add the following dependency to your pom.xml
:
<dependency>
<groupId>com.github.erosb</groupId>
<artifactId>everit-json-schema</artifactId>
<version>1.14.1</version>
</dependency>
Note about older versions: versions between 1.6.0
and 1.9.1
can only be found on JitPack with com.github.everit-org.json-schema:org.everit.json.schema
coordinates. Versions 1.0.0
... 1.5.1
are available on Maven Central under org.everit.json:org.everit.json.schema
coordinates.
There were a couple of attempts to make the library work on Java 6/7.
A java6 port of version 1.9.2 was developed by @mindbender1 and it is accessible through Maven Central with the following coordinates:
<dependency>
<groupId>com.github.erosb</groupId>
<artifactId>everit-json-schema-jdk6</artifactId>
<version>1.9.2</version>
</dependency>
Backports of older versions:
- version 1.4.1 was backported by Doctusoft with coordinates
com.doctusoft:json-schema-java7:1.4.1
- version 1.1.1 was backported by @rdruilhe and is available on JitPack as
com.github.rdruilhe.json-schema:org.everit.json.schema:1.1.1
import org.everit.json.schema.Schema;
import org.everit.json.schema.loader.SchemaLoader;
import org.json.JSONObject;
import org.json.JSONTokener;
// ...
try (InputStream inputStream = getClass().getResourceAsStream("/path/to/your/schema.json")) {
JSONObject rawSchema = new JSONObject(new JSONTokener(inputStream));
Schema schema = SchemaLoader.load(rawSchema);
schema.validate(new JSONObject("{\"hello\" : \"world\"}")); // throws a ValidationException if this object is invalid
}
JSON Schema has currently 4 major releases, Draft 3, Draft 4, Draft 6 and Draft 7. This library implements the 3 newer ones, you can have a quick look at the differences here and here. Since the two versions have a number of differences - and draft 6 is not backwards-compatible with draft 4 - it is good to know which version will you use.
The best way to denote the JSON Schema version you want to use is to include its meta-schema URL in the document root with the "$schema"
key. This is a common notation, facilitated by the library to determine which version should be used.
Quick reference:
- if there is
"$schema": "http://json-schema.org/draft-04/schema"
in the schema root, then Draft 4 will be used - if there is
"$schema": "http://json-schema.org/draft-06/schema"
in the schema root, then Draft 6 will be used - if there is
"$schema": "http://json-schema.org/draft-07/schema"
in the schema root, then Draft 7 will be used - if none of these is found then Draft 4 will be assumed as default
If you want to specify the meta-schema version explicitly then you can change the default from Draft 4 to Draft 6 / 7 by configuring the loader this way:
SchemaLoader loader = SchemaLoader.builder()
.schemaJson(yourSchemaJSON)
.draftV6Support() // or draftV7Support()
.build();
Schema schema = loader.load().build();
Starting from version 1.1.0
the validator collects every schema violations (instead of failing immediately on the first
one). Each failure is denoted by a JSON pointer, pointing from the root of the document to the violating part. If more
than one schema violations have been detected, then a ValidationException
will be thrown at the most common parent
elements of the violations, and each separate violations can be obtained using the ValidationException#getCausingExceptions()
method.
To demonstrate the above concepts, lets see an example. Lets consider the following schema:
{
"type" : "object",
"properties" : {
"rectangle" : {"$ref" : "#/definitions/Rectangle" }
},
"definitions" : {
"size" : {
"type" : "number",
"minimum" : 0
},
"Rectangle" : {
"type" : "object",
"properties" : {
"a" : {"$ref" : "#/definitions/size"},
"b" : {"$ref" : "#/definitions/size"}
}
}
}
}
The following JSON document has only one violation against the schema (since "a" cannot be negative):
{
"rectangle" : {
"a" : -5,
"b" : 5
}
}
In this case the thrown ValidationException
will point to #/rectangle/a
and it won't contain sub-exceptions:
try {
schema.validate(rectangleSingleFailure);
} catch (ValidationException e) {
// prints #/rectangle/a: -5.0 is not higher or equal to 0
System.out.println(e.getMessage());
}
Now - to illustrate the way that multiple violations are handled - let's consider the following JSON document, where both the "a" and "b" properties violate the above schema:
{
"rectangle" : {
"a" : -5,
"b" : "asd"
}
}
In this case the thrown ValidationException
will point to #/rectangle
, and it has 2 sub-exceptions, pointing to
#/rectangle/a
and #/rectangle/b
:
try {
schema.validate(rectangleMultipleFailures);
} catch (ValidationException e) {
System.out.println(e.getMessage());
e.getCausingExceptions().stream()
.map(ValidationException::getMessage)
.forEach(System.out::println);
}
This will print the following output:
#/rectangle: 2 schema violations found
#/rectangle/a: -5.0 is not higher or equal to 0
#/rectangle/b: expected type: Number, found: String
Since version 1.4.0
it is possible to print the ValidationException
instances as
JSON-formatted failure reports. The ValidationException#toJSON()
method returns a JSONObject
instance with the
following keys:
"message"
: the programmer-friendly exception message (description of the validation failure)"keyword"
: the JSON Schema keyword which was violated"pointerToViolation"
: a JSON Pointer denoting the path from the input document root to its fragment which caused the validation failure"schemaLocation"
: a JSON Pointer denoting the path from the schema JSON root to the violated keyword"causingExceptions"
: a (possibly empty) array of sub-exceptions. Each sub-exception is represented as a JSON object, with the same structure as described in this listing. See more above about causing exceptions.
Please take into account that the complete failure report is a hierarchical tree structure: sub-causes of a cause can
be obtained using #getCausingExceptions()
.
ValidationListener
s can serve the purpose of resolving ambiguity about how does an instance JSON match (or does not match)
against a schema. You can attach a ValidationListener
implementation to the validator to receive event notifications about intermediate
success/failure results.
Example:
import org.everit.json.schema.Validator;
...
Validator validator = Validator.builder()
.withListener(new YourValidationListenerImplementation())
.build();
validator.performValidation(schema, input);
The currently supported events:
- a
"$ref"
reference being resolved - a subschema under an
"allOf"
/"anyOf"
/"oneOf"
schema matching - a subschema under an
"allOf"
/"anyOf"
/"oneOf"
schema failing to match - an
"if"
schema matching - an
"if"
schema failing to match - an
"then"
schema matching - an
"then"
schema failing to match - an
"else"
schema matching - an
"else"
schema failing to match
See the javadoc of the org.everit.json.schema.event.ValidationListener
interface for more details. The particular event classes also have
proper #toJSON()
and #toString()
implementations so you can print them in an easily parse-able format.
By default the validation error reporting in collecting mode (see the "Investigating failures" chapter). That is convenient for having a detailed error report, but under some circumstances it is more appropriate to stop the validation when a failure is found without checking the rest of the JSON document. To toggle this fast-failing validation mode
- you have to explicitly build a
Validator
instance for your schema instead of callingSchema#validate(input)
- you have to call the
failEarly()
method ofValidatorBuilder
Example:
import org.everit.json.schema.Validator;
...
Validator validator = Validator.builder()
.failEarly()
.build();
validator.performValidation(schema, input);
Note: the Validator
class is immutable and thread-safe, so you don't have to create a new one for each validation, it is enough
to configure it only once.
In some cases, when validating numbers or booleans, it makes sense to accept string values that are parseable as such primitives, because any successive processing will also automatically parse these literals into proper numeric and logical values. Also, non-string primitive values are trivial to convert to strings, so why not to permit any json primitives as strings?
For example, let's take this schema:
{
"properties": {
"booleanProp": {
"type": "boolean"
},
"integerProp": {
"type": "integer"
},
"nullProp": {
"type": "null"
},
"numberProp": {
"type": "number"
},
"stringProp": {
"type": "string"
}
}
}
The following JSON document fails to validate, although all of the strings could easily be converted into appropriate values:
{
"numberProp": "12.34",
"integerProp": "12",
"booleanProp": "true",
"nullProp": "null",
"stringProp": 12.34
}
In this case, if you want the above instance to pass the validation against the schema, you need to use the lenient primitive validation configuration turned on. Example:
import org.everit.json.schema.*;
...
Validator validator = Validator.builder()
.primitiveValidationStrategry(PrimitiveValidationStrategy.LENIENT)
.build();
validator.performValidation(schema, input);
Note: in lenient parsing mode, all 22 possible boolean literals will be accepted as logical values.