• Stars
    star
    849
  • Rank 51,603 (Top 2 %)
  • Language
    Java
  • License
    Apache License 2.0
  • Created over 8 years ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

JSON Schema validator for java, based on the org.json API

JSON Schema Validator

Deprecation notice

This library is currently in maintenance mode and superseded by erosb/json-sKema.

This repository and won't see any new features. It provides solid support for draft-04, draft-06 and draft-07 versions of the JSON Schema specification.

The latest draft 2020-12 is supported only by erosb/json-sKema.

Apache 2.0 License Build Status Coverage Status

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.

When to use this library?

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.

Maven installation

Add the following dependency to your pom.xml:

<dependency>
	<groupId>com.github.erosb</groupId>
	<artifactId>everit-json-schema</artifactId>
	<version>1.14.4</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.

Java6/7 versions

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

Quickstart

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
}

Draft 4, Draft 6 or Draft 7?

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();

Investigating failures

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

JSON report of the failures

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() .

ValidationListeners - Tracking the validation process

ValidationListeners 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.

Early failure mode

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 calling Schema#validate(input)
  • you have to call the failEarly() method of ValidatorBuilder

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.

Lenient mode

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.

Default values

The JSON Schema specification defines the "default" keyword for denoting default values, though it doesn't explicitly state how it should affect the validation process. By default this library doesn't set the default values, but if you need this feature, you can turn it on by the SchemaLoaderBuilder#useDefaults(boolean) method, before loading the schema:

{
  "properties": {
    "prop": {
      "type": "number",
      "default": 1
    }
  }
}
JSONObject input = new JSONObject("{}");
System.out.println(input.get("prop")); // prints null
Schema schema = SchemaLoader.builder()
	.useDefaults(true)
	.schemaJson(rawSchema)
	.build()
	.load().build();
schema.validate(input);
System.out.println(input.get("prop")); // prints 1

If there are some properties missing from input which have "default" values in the schema, then they will be set by the validator during validation.

RegExp Implementations

For supporting the "regex" keyword of JSON Schema the library offers two possible implementations:

  • the default is based on the java.util.regex package
  • the other one is based on the RE2J library

While the RE2J library provides significantly better performance than java.util.regex, it is not completely compatible with the syntax supported by java.util or ECMA 262. So RE2J is recommended if you are concerned about performance and its limitations are acceptable.

The RE2J implementation can be activated with the SchemaLoaderBuilder#regexpFactory() call:

SchemaLoader loader = SchemaLoader.builder()
    .regexpFactory(new RE2JRegexpFactory())
    // ...
    .build();

Notes:

  • if you don't need the RE2J implementation, it is recommended to exclude it in your pom.xml so it doesn't increase your artifact's size unnecessarily
  • version history: in versions 1.0.0 ... 1.7.0 the java.util implementation was used, in 1.8.0 the RE2J implementation was used, and in 1.9.0 we made it configurable, due to some reported regressions.

readOnly and writeOnly context

The library supports the readOnly and writeOnly keywords which first appeared in Draft 7. If you want to utilize this feature, then before validation you need to tell the validator if the validation happens in read or write context. Example:

schema.json:

{
   "properties": {
     "id": {
       "type": "number",
       "readOnly": true
     }
   }  
}

Validation code snippet:

Validator validator = Validator.builder()
                .readWriteContext(ReadWriteContext.WRITE)
                .build();

validator.performValidation(schema, new JSONObject("{\"id\":42}"));

In this case we told the validator that the validation happens in WRITE context, and in the input JSON object the "id" property appears, which is marked as "readOnly" in the schema, therefore this call will throw a ValidationException.

Format validators

Starting from version 1.2.0 the library supports the "format" keyword (which is an optional part of the specification).

The supported formats vary depending on the schema spec version you use (since the standard formats were introduced in different versions on the validation specification).

Here is a compatibility table of the supported standard formats:

Draft 4 Draft 6 Draft 7
date-time ✅ ✅ ✅
email ✅ ✅ ✅
hostname ✅ ✅ ✅
ipv4 ✅ ✅ ✅
ipv6 ✅ ✅ ✅
uri ✅ ✅ ✅
uri-reference ✅ ✅
uri-template ✅ ✅
json-pointer ✅ ✅
date ✅
time ✅
regex ✅
relative-json-pointer ✅

The library also supports adding custom format validators. To use a custom validator basically you have to

  • create your own validation in a class implementing the org.everit.json.schema.FormatValidator interface
  • bind your validator to a name in a org.everit.json.schema.loader.SchemaLoader.SchemaLoaderBuilder instance before loading the actual schema

Example

Lets assume the task is to create a custom validator which accepts strings with an even number of characters.

The custom FormatValidator will look something like this:

public class EvenCharNumValidator implements FormatValidator {

  @Override
  public Optional<String> validate(final String subject) {
    if (subject.length() % 2 == 0) {
      return Optional.empty();
    } else {
      return Optional.of(String.format("the length of string [%s] is odd", subject));
    }
  }

}

To bind the EvenCharNumValidator to a "format" value (for example "evenlength") you have to bind a validator instance to the keyword in the schema loader configuration:

JSONObject rawSchema = new JSONObject(new JSONTokener(inputStream));
SchemaLoader schemaLoader = SchemaLoader.builder()
	.schemaJson(rawSchema) // rawSchema is the JSON representation of the schema utilizing the "evenlength" non-standard format
	.addFormatValidator("evenlength", new EvenCharNumValidator()) // the EvenCharNumValidator gets bound to the "evenlength" keyword
	.build();
Schema schema = schemaLoader.load().build(); // the schema is created using the above created configuration
schema.validate(jsonDocument);  // the document validation happens here

$ref resolution

In a JSON Schema document it is possible to use relative URIs to refer previously defined types. Such references are expressed using the "$ref" and "$id" keywords. While the specification describes resolution scope alteration and dereferencing in detail, it doesn't explain the expected behavior when the first occurring "$ref" or "$id" is a relative URI.

In the case of this implementation it is possible to explicitly define an absolute URI serving as the base URI (resolution scope) using the appropriate builder method:

SchemaLoader schemaLoader = SchemaLoader.builder()
        .schemaJson(jsonSchema)
        .resolutionScope("http://example.org/") // setting the default resolution scope
        .build();

Loading from the classpath

As your schemas grow you will want to split that up into multiple source files and wire them with "$ref" references. If you want to store the schemas on the classpath (instead of eg. serving them through HTTP) then the recommended way is to use the classpath: protocol to make the schemas reference each other. To make the classpath: protocol work:

  • if you use the Spring framework you don't have to do anything, spring installs the necessary protocol handler out of the box
  • otherwise you can utilize the library's built-in classpath-aware SchemaClient, example:
SchemaLoader schemaLoader = SchemaLoader.builder()
        .schemaClient(SchemaClient.classPathAwareClient())
        .schemaJson(jsonSchema)
        .resolutionScope("classpath://my/schemas/directory/") // setting the default resolution scope
        .build();

Given this configuration, the following references will be properly resolved in jsonSchema:

{
    "properties": {
        "sameDir": { "$ref": "sameDirSchema.json" },
        "absPath": { "$ref": "classpath://somewhere/else/otherschema.json" },
        "httpPath": { "$ref": "http://example.org/http-works-as-usual" },
    }
}

and sameDirSchema.json will be looked for in /my/schemas/directory/sameDirSchema.json on the classpath.

Registering schemas by URI

Sometimes it is useful to work with preloaded schemas, to which we assign an arbitary URI (maybe an uuid) instead of loading the schema through a URL. This can be done by assigning the schemas to a URI with the #registerSchemaByURI() method of the schema loader. Example:

SchemaLoader schemaLoader = SchemaLoader.builder()
        .registerSchemaByURI(new URI("urn:uuid:a773c7a2-1a13-4f6a-a70d-694befe0ce63"), aJSONObject)
        .registerSchemaByURI(new URI("http://example.org"), otherJSONObject)
        .schemaJson(jsonSchema)
        .resolutionScope("classpath://my/schemas/directory/")
        .build();

Notes:

  • the passed schema object must be a JSONObject or a Boolean (the formal parameter type is Object only because these two don't have any other common superclass).
  • if you want, you can pass a URL with HTTP protocol, it is still a valid URI. Since in this case you pre-assigned a schema to a URI, there will be no network call made. This can be a caching strategy (though defining your own SchemaClient implementation works too, or you can even utilize the extensible protocol handling of the java.net package)

Excluding dependencies

Some of the dependencies can be excluded from the library, and it still remains usable, with some limitations:

  • if you exclude the com.damnhandy:handy-uri-templates dependency, then your schema shouldn't use the "uri-template" format
  • if you exclude the commons-validator:commons-validator dependency, then your schema shouldn't use the following formats: "email", "ipv4", "ipv6", "hostname"

Javadoc

By library version:

The generated javadoc of versions 1.0.0 - 1.5.1 is available at javadoc.io

For the versions in between (1.6.0 - 1.9.1) it isn't published anywhere.

More Repositories

1

jira-timetracker-plugin

A plugin that makes it easier to fill the timelogs in Jira
Java
27
star
2

jira-worklog-query-plugin

A JIRA plugin to query worklogs in JSON format via an authenticted RESTful service.
Java
17
star
3

javaagent-shutdown

A Java Agent that calls System.exit() after installed.
Java
7
star
4

eosgi-maven-plugin

A Maven plugin that runs Integration Tests inside OSGi containers and collects OSGi Bundle dependencies to be able to use them inside any IDE.
Java
4
star
5

i18n-props-xls-converter

Java
4
star
6

osgi-richconsole

A simple bundle that opens up a rich client application if the OSGi server is started in a graphical environment. This module can be very helpful during development.
Java
4
star
7

jira-querydsl-support

Java
2
star
8

atlassian-restclient-jiracloud

Generated Jira Cloud REST client API in Java.
Java
2
star
9

cache-lru-ecm

Simple LRU cache implementation based on a high performance ConcurrentLinkedHashMap.
2
star
10

osgi-xmlcommons-full

Supporting all XMLCommons API within OSGi attached with their default implementations.
Java
2
star
11

osgi-testrunner

Runs JUnit test objects that are exposed as OSGi Services
Java
2
star
12

osgi-remote-jersey

A solution that catches OSGi services that are registered based on the Remote Services chapter of OSGi Enterprise specification and distributes them with Jersey via the http whiteboard pattern.
Java
2
star
13

everit-httpclient

Asynchronous HTTP Client API that can be a wrapper around any of the well known HTTP client libraries in Java.
Java
2
star
14

password-encryptor-pbkdf2

PBKDF2 based implementation of the Password Encryptor API
Java
2
star
15

osgi-loglistener-slf4j

An OSGi LogListener that forwards the LogEntry to an Slf4j Logger.
Java
2
star
16

atlassian-dev-resource-provider

Java
1
star
17

email-javamail-dkim

DKIM enhancer for Everit Email Javamail implementation
Java
1
star
18

email-javamail

JSR 919 (javax.mail) based implementation of email-api
Java
1
star
19

authentication-oauth2-ri

Java
1
star
20

jira-hr-admin

Configuration screens and tables of several Jira plugins that are provided by Everit.
Java
1
star
21

commons-dbcp-ds

A configurable DS component that makes it possible to create XA and non-XA pooled datasource instances.
Java
1
star
22

cache-api

1
star
23

templating-html

Everit Web Templating
Java
1
star
24

transaction-propagator-jta

JTA based implementation of transaction-propagator-api
Java
1
star
25

coding-conventions

A set of conventions, recommendations, guidelines and configuration files for Checkstyle and Eclipse to be able to keep the structural quality of the code high.
1
star
26

osgi-capability-collector

Helper classes for collecting OSGi service references based on clauses and call methods based on reference availability changes
Java
1
star
27

authentication-oauth2-ri-ecm

Java
1
star
28

eventdispatcher

Utility classes to be able to write an event dispatcher that sends historical "replay" event data to newly registered listeners as well.
Java
1
star
29

web-partialresponse-jira

A Jira Plugin that contains the PartialResponse class and Javascript as Jira Web-Resource
Java
1
star
30

transaction-propagator-api

API for transaction helper
Java
1
star
31

jira-custom-permission

A Jira plugin that supports the registration of custom permissions by other plugins and gives a management screen to assign the custom permissions to users and groups.
Java
1
star
32

ecm-extender-ri

Implementation of ECM extender that picks up annotated ECM components based on bundle capabilities.
Java
1
star
33

prioritization-custom-field-fogre

This simple Forge app adds a prioritization custom field that you can configure on your issue screens in classic projects. The purpose of the app is to demonstrate how to develope quickly serverless applications with Atlassian Forge.
JavaScript
1
star