Skip to content

harrel-test/json-schema

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

json-schema

build maven javadoc Coverage

Java library implementing JSON schema specification:

  • compatible with Java 8,
  • support for the newest specification draft (2020-12) Supported spec Compliance,
  • support for custom keywords,
  • support for annotation collection,
  • multiple JSON providers to choose from (supported JSON libraries)
  • and no additional dependencies on top of that.

Demo

You can check out how it works here.

Installation

Please note that you will also need to include at least one of the supported JSON provider libraries (see JSON provider setup).

Maven

<dependency>
    <groupId>dev.harrel</groupId>
    <artifactId>json-schema</artifactId>
    <version>1.3.0</version>
</dependency>

Gradle

implementation 'dev.harrel:json-schema:1.3.0'

Usage

To validate JSON against a schema, you just need to invoke:

String schema = """
        {
          "type": "boolean"
        }""";
String instance = "true";
boolean valid = new ValidatorFactory().validate(schema, instance).isValid();

Validation result could be queried for more verbose output than a simple boolean flag:

Validator.Result result = new ValidatorFactory().validate(schema, instance);
boolean valid = result.isValid(); // Boolean flag indicating if validation succeeded
List<Error> errors = result.getErrors(); // Details where validation exactly failed
List<Annotation> annotations = result.getAnnotations(); // Collected annotation during validation process

Error and Annotation classes contain specific information where the event occurred, along with error message or annotation value. For specific structure details please refer to the documentation.

Reusing schema

Probably most common case is to validate multiple JSON objects against one specific schema. Approach listed above parses schema for each validation request. To avoid this performance hit, it is better to use Validator class directly.

Validator validator = new ValidatorFactory().createValidator();
URI schemaUri = validator.registerSchema(schema); // Returns URI which should be used to refer to this schema
Validator.Result result1 = validator.validate(schemaUri, instance1);
Validator.Result result2 = validator.validate(schemaUri, instance2);

This way, schema is parsed only once. You could also register multiple schemas this way and refer to them independently. Keep in mind that the "registration space" for schemas is common for one Validator - this can be used to refer dynamically between schemas.

Limitations

Features that are not supported yet:

  • $vocabulary keyword - all vocabularies' related semantics are not yet there. Vocabularies are supported from version 1.2.0 - see more.
  • format keyword - the specification doesn't require format to perform any validations. Support for official format validation might be added in future versions. Meanwhile, the implementation could be provided by user (see adding custom keywords).

JSON providers

Supported providers:

  • com.fasterxml.jackson.core:jackson-databind (default),
  • com.google.code.gson:gson,
  • jakarta.json:jakarta.json-api,
  • org.json:json,
  • new.minidev:json-smart,
  • org.codehouse.jettison:jettison,
  • org.apache.tapestry:tapestry-json - planned,
  • javax.json:javax.json-api - planned.

The default provider is com.fasterxml.jackson.core:jackson-databind, so if you are not planning on changing the ValidatorFactory configuration, you need to have this dependency present in your project.

Specific version of provider dependencies which were tested can be found in project POM (uploaded to maven central) listed as optional dependencies.

All adapter classes for JSON provider libs can be found in this package. Anyone is free to add new adapter classes for any JSON lib of their choice, but keep in mind that it is not trivial. If you do so, ensure that test suites for providers pass.

Changing JSON provider

Provider Tested version Factory class Provider node class
com.fasterxml.jackson.core:jackson-databind 2.15.2 dev.harrel.providers.JacksonNode.Factory com.fasterxml.jackson.databind.JsonNode
com.google.code.gson:gson 2.10.1 dev.harrel.providers.GsonNode.Factory com.google.gson.JsonElement
jakarta.json:jakarta.json-api 2.1.2 (with org.eclipse.parsson:parsson:1.1.2) dev.harrel.providers.JakartaJsonNode.Factory jakarta.json.JsonValue
org.json:json 20230227 dev.harrel.providers.OrgJsonNode.Factory
new.minidev:json-smart 2.4.11 dev.harrel.providers.JsonSmartNode.Factory
  • net.minidev.json.JSONObject,
  • net.minidev.json.JSONArray,
  • literal types.
org.codehouse.jettison:jettison 1.5.4 dev.harrel.providers.JettisonNode.Factory
  • org.codehaus.jettison.json.JSONObject,
  • org.codehaus.jettison.json.JSONArray,
  • literal types.

com.fasterxml.jackson.core:jackson-databind

new ValidatorFactory().withJsonNodeFactory(new JacksonNode.Factory());

com.google.code.gson:gson

new ValidatorFactory().withJsonNodeFactory(new GsonNode.Factory());

jakarta.json:jakarta.json-api

Keep in mind that this library contains only interfaces without concrete implementation. It would be required to also have e.g. org.glassfish:jakarta.json dependency in your classpath. Although, it was tested with newest jakarta.json-api version, it should be compatible down to 1.1 version.

new ValidatorFactory().withJsonNodeFactory(new JakartaJsonNode.Factory());

org.json:json

new ValidatorFactory().withJsonNodeFactory(new OrgJsonNode.Factory());

new.minidev:json-smart

new ValidatorFactory().withJsonNodeFactory(new JsonSmartNode.Factory());

org.codehouse.jettison:jettison

new ValidatorFactory().withJsonNodeFactory(new JettisonNode.Factory());

Provider literal types

Some providers don't have a single wrapper class for their JSON node representation:

  • org.json:json,
  • new.minidev:json-smart,
  • org.codehouse.jettison:jettison,

and they represent literal nodes with these classes:

  • java.lang.String,
  • java.lang.Boolean,
  • java.lang.Character,
  • java.lang.Enum,
  • java.lang.Integer,
  • java.lang.Long,
  • java.lang.Double,
  • java.math.BigInteger,
  • java.math.BigDecimal.

Advanced configuration

Resolving external schemas

By default, the only schemas that are resolved externally, are specification meta-schemas (e.g. https://json-schema.org/draft/2020-12/schema) which are used for validating schemas during registration process. The meta-schema files are fetched from the classpath and are packaged with jar.

There is no mechanism to pull schemas via HTTP requests. If such behaviour is required it should be implemented by the user.

Providing custom SchemaResolver would look like this:

SchemaResolver resolver = (String uri) -> {
    if ("urn:my-schema1".equals(uri)) {
        // Here goes the logic to retrieve this schema
        // This may be e.g. HTTP call
        String rawSchema = ...
        return SchemaResolver.Result.fromString(rawSchema);
    } else if ("urn:my-schema2".equals(uri)) {
        // Same thing here
        String rawSchema = ...
        return SchemaResolver.Result.fromString(rawSchema);
    } else {
        return SchemaResolver.Result.empty();
    }
};

Then it just needs to be attached to ValidatorFactory:

new ValidatorFactory().withSchemaResolver(resolver);

For more information about return type please refer to the documentation.

Dialects

By default, draft 2020-12 dialect is used, but it can be changed with:

new ValidatorFactory().withDialect(new Dialects.Draft2020Dialect()); // or any other dialect

Custom dialects are also supported, see more here.

Meta-schemas

Dialects come with their meta-schemas. Each schema will be validated by meta-schema provided by used dialect. If validation fails InvalidSchemaException is thrown.

For each specific schema this behaviour can be overridden by providing $schema keyword with desired meta-schema URI. Resolution of meta-schema follows the same rules as for a regular schema.

There is a configuration option that disables all schema validations (affects $schema and vocabularies semantics too):

new ValidatorFactory().withDisabledSchemaValidation(true);

Adding custom keywords

Customizing specific keywords behaviour can be achieved by providing custom EvaluatorFactory implementation. Each dialect comes with its core EvaluatorFactory which will always be used, but additional EvaluatorFactory implementation can be provided on top of that. If you want to completely alter how schemas are validated, please refer to custom dialects.

This example shows an implementation that adds customKeyword keyword handling which fails validation if JSON node is not an empty array:

class CustomEvaluatorFactory implements EvaluatorFactory {
    @Override
    public Optional<Evaluator> create(SchemaParsingContext ctx, String fieldName, JsonNode node) {
        if ("customKeyword".equals(fieldName)) {
            return Optional.of((evaluationContext, instanceNode) -> {
                if (instanceNode.isArray() && instanceNode.asArray().isEmpty()) {
                    return Evaluator.Result.success(); // Optionally, you could also pass annotation
                } else {
                    return Evaluator.Result.failure(); // Optionally, you could also pass error message
                }
            });
        }
        return Optional.empty();
    }
}

Then it just needs to be attached to ValidatorFactory:

new ValidatorFactory().withEvaluatorFactory(new CustomEvaluatorFactory());

Custom dialects

If you want you could provide your custom dialect configuration:

Dialect customDialect = new Dialect() {
    @Override
    public SpecificationVersion getSpecificationVersion() {
        return SpecificationVersion.DRAFT2020_12;
    }
    
    @Override
    public String getMetaSchema() {
        return "https://example.com/custom/schema";
    }
    
    @Override
    public EvaluatorFactory getEvaluatorFactory() {
        return new Draft2020EvaluatorFactory();
    }
    
    @Override
    public Set<String> getSupportedVocabularies() {
        return Collections.singleton("custom-vocabulary");
    }
    
    @Override
    public Set<String> getRequiredVocabularies() {
        return Collections.emptySet();
    }
    
    @Override
    public Map<String, Boolean> getDefaultVocabularyObject() {
        return Collections.singletonMap("custom-vocabulary", true);
    }
};
new ValidatorFactory().withDialect(customDialect);

See the documentation for more details.

About

JSON schema validation library written in Java

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 100.0%