Snow, a full-featured JSON Schema validator
55
stars
527
commits
Java
primary language
Jun 21, 2024
updated
Version: 0.16.0
The main goal of this project is to be a reference JSON Schema validator. While it provides a few working applications, it's meant primarily as an API for building your own toolset.
See: JSON Schema
This project has the following features:
These additional features exist:
There are more details below, but here are four commands that will get you started right away:
mvn compile exec:java@main -Dexec.args="schema.json instance.json"
The two files in this example are named schema.json for the schema and
instance.json for the instance. The example assumes the files are in the
current working directory.mvn compile exec:java@test -Dexec.args="/suites/json-schema-test-suite"
This assumes that the test suite is in /suites/json-schema-test-suite.
Yours may be in a different location. The test suite can be cloned from
JSON Schema Test Suite.mvn compile exec:java@linter -Dexec.args="schema.json"
The schema file in this example is named schema.json. The example assumes
the file is in the current working directory.mvn compile exec:java@coverage -Dexec.args="schema.json instance.json"
The two files in this example are named schema.json for the schema and
instance.json for the instance. The example assumes the files are in the
current working directory.This project uses Google's Gson library under the hood for JSON parsing. ClassGraph is used to support class finding.
This means these things:
This project follows just about everything it can from the latest JSON Schema specification draft. There are a few things it does slightly differently due to some implementation details.
\Z boundary
matcher but ECMA 262 does not.There are a few ways the validator determines which specification to use when processing and validating a schema. The steps are as follows:
SPECIFICATION option or any default.DEFAULT_SPECIFICATION option or any default.This section describes options that control the validator behaviour.
All options are defined in the com.qindesign.json.schema.Option class, and
their use is in com.qindesign.json.schema.Options.
Some options are specification-specific, meaning they have different defaults depending on which specification is applied. Everything else works as expected: users set or remove options. It is only the internal defaults that have any specification-specific meanings.
There are two ways to retrieve an option. Both are similar, except one of the ways checks the specification-specific defaults before the non-specification-specific defaults. The steps are as follows, where subsequent steps are followed only if the current step is not successful.
Specification-specific consultation steps, using a specific specification:
Non-specification-specific consultation steps:
Type: java.lang.Boolean
This controls whether the validator should attempt auto-resolution when searching for schemas or when otherwise resolving IDs. This entails adding the original base URI and any root $id as known URLs during validation.
Type: java.lang.Boolean
This controls, if annotations are collected, whether they should also be retained for failed schemas. This option only has an effect when annotations are being collected.
Type: java.lang.Boolean
This controls whether to treat the "content" values as assertions in Draft-07. This only includes "contentEncoding" and "contentMediaType".
Type: com.qindesign.json.schema.Specification
This option specifies the default specification to follow if one cannot be determined from a schema, either by an explicit indication, or by heuristics. This is the final fallback specification.
Type: java.lang.Boolean
This is a specification-specific option meaning its default is different depending on which specification is being used. It controls whether to treat "format" values as assertions.
Type: com.qindesign.json.schema.Specification
This indicates which specification to use if one is not explicitly stated in a schema.
This project is designed to provide APIs and tools for performing JSON Schema validation. Its main purpose is to do most of the work, but have the user wire in everything themselves. A few rudimentary and runnable test programs are provided, however.
The main package is com.qindesign.json.schema.
This project defines a module and exports these packages:
com.qindesign.json.schema: This is the main validation package.com.qindesign.json.schema.net: Provides some URI and hostname
processing tools.It also transitively requires this package:
com.google.gsonThe first program is Main. This takes two arguments, a schema file and an
instance file, and then performs validation of the instance against the schema.
The second program is Test. This takes one argument, a directory containing
the JSON Schema test suite, and then runs all the tests in the suite. You can
obtain a copy of the test suite by cloning the
test suite repository.
The third program is Linter, a rudimentary linter for JSON Schema files. It
takes one argument, the schema file to check.
The fouth program is Coverage, a simple coverage tool for JSON Schemas and
instances. It's similar to Main, but prints different output.
The main entry point to the API is the Validator constructor and validate
method. In addition to the non-optional schema, instance, and base URI, you can
pass options, known IDs and URLs, and a place to put collected annotations and
errors.
In this version, the caller must organize the errors into the desired output
format. An example of how to convert them into the Basic output format is in
the Main.basicOutput method.
Providing tools to format the errors into more output formats may happen in the future.
Annotations and errors are collected by optionally providing maps to
Validator.validate. They're maps from instance locations to an associated
Annotation object, with some intervening values.
Annotation. The Annotation value is
dependent on the source of the annotation.Annotation. The Annotation value is a ValidationResult object,
and its name will be "error" when the result is false and "annotation" when
the result is true.For annotations, Annotation.isValid() indicates whether the annotation is
considered valid or auxiliary. When
failed annotations are collected,
invalid annotations indicate an annotation that would otherwise exist if the
associated schema had not failed.
For errors, Error.isPruned() means that the result is not relevant to the
schema result. For example, "oneOf" will pass validation if one subschema passes
and all the other subschemas fail. All failing subschemas will indicate an
error, but it will be marked as pruned.
This is useful to track coverage vs. a minimal set of useful errors.
The Results class provides some tools for sorting and collecting annotations
and errors. It does the work of extracting a list of useful results.
The locations are given as JSON Pointers.
The annotation types for specific keywords are as follows:
java.lang.Boolean, always true if present, indicating
that the subschema was applied to all remaining items in the instance array.java.util.Set<String>, the set of property names
whose contents were validated by this subschema.java.lang.Stringjava.lang.Stringcom.google.gson.JsonElementcom.google.gson.JsonElementjava.lang.Booleanjava.lang.Stringcom.google.gson.JsonArrayjava.lang.Stringjava.lang.Integer, the largest index in the instance to which a
subschema was applied, or java.lang.Boolean (always true) if a subschema
was applied to every index.java.util.Set<String>, the set of property names
matched by this keyword.java.util.Set<String>, the set of property names matched by
this keyword.java.lang.Booleanjava.lang.Stringjava.lang.Boolean, always true if present, indicating
that the subschema was applied to all remaining items in the instance array.java.util.Set<String>, the set of property names
whose contents were validated by this subschema.java.lang.BooleanThere are a few internal APIs that may be useful for your own projects, outside of schema validation. Note that these are subject to change.
com.qindesign.json.schema.util.Base64InputStream: Converts a Base64-encoded
string into a byte stream.com.qindesign.json.schema.util.LRUCache: An
LRU cache
implementation.com.qindesign.json.schema.net.Hostname: Parses regular and IDN hostnames.com.qindesign.json.schema.net.URI: An
RFC 3986-compliant URI parser.
As of this writing, Java's URI API is only RFC 2396-compliant and is not
sufficient for processing JSON Schemas.com.qindesign.json.schema.net.URIParser.parseIPv6: Parses IPv6 addresses,
per RFC 3986.com.qindesign.json.schema.net.URIParser.parseIPv4: Parses IPv4 addresses,
per RFC 3986.Please consult the Javadocs for those classes and methods for more information.
This project uses Maven as its build tool because it makes managing the dependencies easy. It uses standard Maven commands and phases. For example, to compile the project, use:
mvn compile
To clean and then re-compile:
mvn clean compile
Maven makes it easy to build, execute, and package everything with the right dependencies, however it's also possible to use your IDE or different tools to manage the project. This section only discusses Maven usage.
Maven takes care of project dependencies for you so you don't have to manage the classpath or downloads.
Currently, there are four predefined execution targets:
main: Executes Main. Validates an instance against a schema.test: Executes Test. Runs the test suite.linter: Executes Linter. Checks a schema.coverage: Executes Coverage. Does a schema coverage check
after validation.This section shows some simple execution examples. There's more information about the included programs below.
Note that Maven doesn't automatically build the project when running an
execution target. It either has to be pre-built using compile or added to the
command line. For example, to compile and then run the linter:
mvn compile exec:java@linter -Dexec.args="schema.json"
To run the main validator without attempting a compile first, say because it's already built:
mvn exec:java@main -Dexec.args="schema.json instance.json"
To compile and run the test suite and tell the test runner that the suite is
in /suites/json-schema-test-suite:
mvn compile exec:java@test -Dexec.args="/suites/json-schema-test-suite"
To execute a specific main class, say one that isn't defined as a specific
execution, add an exec.mainClass property. For example, if the fully-qualified
main class is my.Main and it takes some "program arguments":
mvn exec:java -Dexec.mainClass="my.Main" -Dexec.args="program arguments"
Snow is available from the Maven Central Repository. To include it in your own programs, add the following dependency:
<dependency>
<groupId>com.qindesign</groupId>
<artifactId>snowy-json</artifactId>
<version>0.15.0</version>
</dependency>
The linter's job is to provide suggestions about potential errors in a schema. It shows only potential problems whose presence does not necessarily mean the schema won't work.
The linter is rudimentary and does not check or validate everything about the schema. It does currently check for the following things:
format values.items arrays.additionalItems without a sibling array-form items.$schema elements inside a subschema that do not have a sibling $id.$id values.$ref values that don't exist.minLength and maxLength.exclusiveMinimum is not strictly less than exclusiveMaximum.minimum
expects that the type is "number" or "integer" and format expects a
"string" type.default and const; a type is expected to
exist and to match the implied type for these values.enums.enum, allOf, anyOf, or oneOf.minContains without a sibling contains.maxContains without a sibling contains.unevaluatedItems without a sibling array-form items.$id values that have an empty fragment.then without if.else without if.$ref members with siblings.It's possible to add your own rules to the linter. There are four important concepts to know about when adding rules:
Linter.addStringRule will execute if the current
element is a primitive string.Linter.Context.The following example snippet tests for the existence of any "anyOf" schema keywords:
JsonElement schema;
// ...load the schema...
Linter linter = new Linter();
linter.addRule(context -> {
if (context.isKeyword() && context.is("anyOf")) {
context.addIssue("anyOf detected");
}
});
Map<JSONPath, List<String>> issues = linter.check();
// ...print the issues...
The JSON class has a traverseSchema method that does a preorder tree
traversal for JSON schemas. It's what the linter uses internally. It's also
possible to use this to write your own linting rules.
The following example snippet also tests for the existence of any "anyOf" schema keywords:
JsonElement schema;
// ...load the schema...
JSON.traverseSchema(schema, (e, parent, path, state) -> {
if (!state.isNotKeyword() && path.endsWith("anyOf")) {
System.out.println(path + ": anyOf keyword present");
}
});
The coverage checker works similarly to the main validator, except that after validation, it prints out some coverage results.
It outputs two JSON objects:
There are plans to explore supporting more features, including:
ValidatorContext, i.e.
across calls to Validator.validate.These are plans that may or may not be explored:
I'd love to say this: "The validator isn't wrong, the spec is ambiguous."™
Realistic? No, but fun to say anyway.
Thanks to JetBrains for providing an Open Source licence for IntelliJ, my favourite IDE since forever.
Snow, a JSON Schema validator
Copyright (c) 2020-2021 Shawn Silverman
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Copyright (c) 2020-2021 Shawn Silverman
527 commits
Java
100.0%
Snow, a full-featured JSON Schema validator
55
stars
527
commits
Java
primary language
Jun 21, 2024
updated
Version: 0.16.0
The main goal of this project is to be a reference JSON Schema validator. While it provides a few working applications, it's meant primarily as an API for building your own toolset.
See: JSON Schema
This project has the following features:
These additional features exist:
There are more details below, but here are four commands that will get you started right away:
mvn compile exec:java@main -Dexec.args="schema.json instance.json"
The two files in this example are named schema.json for the schema and
instance.json for the instance. The example assumes the files are in the
current working directory.mvn compile exec:java@test -Dexec.args="/suites/json-schema-test-suite"
This assumes that the test suite is in /suites/json-schema-test-suite.
Yours may be in a different location. The test suite can be cloned from
JSON Schema Test Suite.mvn compile exec:java@linter -Dexec.args="schema.json"
The schema file in this example is named schema.json. The example assumes
the file is in the current working directory.mvn compile exec:java@coverage -Dexec.args="schema.json instance.json"
The two files in this example are named schema.json for the schema and
instance.json for the instance. The example assumes the files are in the
current working directory.This project uses Google's Gson library under the hood for JSON parsing. ClassGraph is used to support class finding.
This means these things:
This project follows just about everything it can from the latest JSON Schema specification draft. There are a few things it does slightly differently due to some implementation details.
\Z boundary
matcher but ECMA 262 does not.There are a few ways the validator determines which specification to use when processing and validating a schema. The steps are as follows:
SPECIFICATION option or any default.DEFAULT_SPECIFICATION option or any default.This section describes options that control the validator behaviour.
All options are defined in the com.qindesign.json.schema.Option class, and
their use is in com.qindesign.json.schema.Options.
Some options are specification-specific, meaning they have different defaults depending on which specification is applied. Everything else works as expected: users set or remove options. It is only the internal defaults that have any specification-specific meanings.
There are two ways to retrieve an option. Both are similar, except one of the ways checks the specification-specific defaults before the non-specification-specific defaults. The steps are as follows, where subsequent steps are followed only if the current step is not successful.
Specification-specific consultation steps, using a specific specification:
Non-specification-specific consultation steps:
Type: java.lang.Boolean
This controls whether the validator should attempt auto-resolution when searching for schemas or when otherwise resolving IDs. This entails adding the original base URI and any root $id as known URLs during validation.
Type: java.lang.Boolean
This controls, if annotations are collected, whether they should also be retained for failed schemas. This option only has an effect when annotations are being collected.
Type: java.lang.Boolean
This controls whether to treat the "content" values as assertions in Draft-07. This only includes "contentEncoding" and "contentMediaType".
Type: com.qindesign.json.schema.Specification
This option specifies the default specification to follow if one cannot be determined from a schema, either by an explicit indication, or by heuristics. This is the final fallback specification.
Type: java.lang.Boolean
This is a specification-specific option meaning its default is different depending on which specification is being used. It controls whether to treat "format" values as assertions.
Type: com.qindesign.json.schema.Specification
This indicates which specification to use if one is not explicitly stated in a schema.
This project is designed to provide APIs and tools for performing JSON Schema validation. Its main purpose is to do most of the work, but have the user wire in everything themselves. A few rudimentary and runnable test programs are provided, however.
The main package is com.qindesign.json.schema.
This project defines a module and exports these packages:
com.qindesign.json.schema: This is the main validation package.com.qindesign.json.schema.net: Provides some URI and hostname
processing tools.It also transitively requires this package:
com.google.gsonThe first program is Main. This takes two arguments, a schema file and an
instance file, and then performs validation of the instance against the schema.
The second program is Test. This takes one argument, a directory containing
the JSON Schema test suite, and then runs all the tests in the suite. You can
obtain a copy of the test suite by cloning the
test suite repository.
The third program is Linter, a rudimentary linter for JSON Schema files. It
takes one argument, the schema file to check.
The fouth program is Coverage, a simple coverage tool for JSON Schemas and
instances. It's similar to Main, but prints different output.
The main entry point to the API is the Validator constructor and validate
method. In addition to the non-optional schema, instance, and base URI, you can
pass options, known IDs and URLs, and a place to put collected annotations and
errors.
In this version, the caller must organize the errors into the desired output
format. An example of how to convert them into the Basic output format is in
the Main.basicOutput method.
Providing tools to format the errors into more output formats may happen in the future.
Annotations and errors are collected by optionally providing maps to
Validator.validate. They're maps from instance locations to an associated
Annotation object, with some intervening values.
Annotation. The Annotation value is
dependent on the source of the annotation.Annotation. The Annotation value is a ValidationResult object,
and its name will be "error" when the result is false and "annotation" when
the result is true.For annotations, Annotation.isValid() indicates whether the annotation is
considered valid or auxiliary. When
failed annotations are collected,
invalid annotations indicate an annotation that would otherwise exist if the
associated schema had not failed.
For errors, Error.isPruned() means that the result is not relevant to the
schema result. For example, "oneOf" will pass validation if one subschema passes
and all the other subschemas fail. All failing subschemas will indicate an
error, but it will be marked as pruned.
This is useful to track coverage vs. a minimal set of useful errors.
The Results class provides some tools for sorting and collecting annotations
and errors. It does the work of extracting a list of useful results.
The locations are given as JSON Pointers.
The annotation types for specific keywords are as follows:
java.lang.Boolean, always true if present, indicating
that the subschema was applied to all remaining items in the instance array.java.util.Set<String>, the set of property names
whose contents were validated by this subschema.java.lang.Stringjava.lang.Stringcom.google.gson.JsonElementcom.google.gson.JsonElementjava.lang.Booleanjava.lang.Stringcom.google.gson.JsonArrayjava.lang.Stringjava.lang.Integer, the largest index in the instance to which a
subschema was applied, or java.lang.Boolean (always true) if a subschema
was applied to every index.java.util.Set<String>, the set of property names
matched by this keyword.java.util.Set<String>, the set of property names matched by
this keyword.java.lang.Booleanjava.lang.Stringjava.lang.Boolean, always true if present, indicating
that the subschema was applied to all remaining items in the instance array.java.util.Set<String>, the set of property names
whose contents were validated by this subschema.java.lang.BooleanThere are a few internal APIs that may be useful for your own projects, outside of schema validation. Note that these are subject to change.
com.qindesign.json.schema.util.Base64InputStream: Converts a Base64-encoded
string into a byte stream.com.qindesign.json.schema.util.LRUCache: An
LRU cache
implementation.com.qindesign.json.schema.net.Hostname: Parses regular and IDN hostnames.com.qindesign.json.schema.net.URI: An
RFC 3986-compliant URI parser.
As of this writing, Java's URI API is only RFC 2396-compliant and is not
sufficient for processing JSON Schemas.com.qindesign.json.schema.net.URIParser.parseIPv6: Parses IPv6 addresses,
per RFC 3986.com.qindesign.json.schema.net.URIParser.parseIPv4: Parses IPv4 addresses,
per RFC 3986.Please consult the Javadocs for those classes and methods for more information.
This project uses Maven as its build tool because it makes managing the dependencies easy. It uses standard Maven commands and phases. For example, to compile the project, use:
mvn compile
To clean and then re-compile:
mvn clean compile
Maven makes it easy to build, execute, and package everything with the right dependencies, however it's also possible to use your IDE or different tools to manage the project. This section only discusses Maven usage.
Maven takes care of project dependencies for you so you don't have to manage the classpath or downloads.
Currently, there are four predefined execution targets:
main: Executes Main. Validates an instance against a schema.test: Executes Test. Runs the test suite.linter: Executes Linter. Checks a schema.coverage: Executes Coverage. Does a schema coverage check
after validation.This section shows some simple execution examples. There's more information about the included programs below.
Note that Maven doesn't automatically build the project when running an
execution target. It either has to be pre-built using compile or added to the
command line. For example, to compile and then run the linter:
mvn compile exec:java@linter -Dexec.args="schema.json"
To run the main validator without attempting a compile first, say because it's already built:
mvn exec:java@main -Dexec.args="schema.json instance.json"
To compile and run the test suite and tell the test runner that the suite is
in /suites/json-schema-test-suite:
mvn compile exec:java@test -Dexec.args="/suites/json-schema-test-suite"
To execute a specific main class, say one that isn't defined as a specific
execution, add an exec.mainClass property. For example, if the fully-qualified
main class is my.Main and it takes some "program arguments":
mvn exec:java -Dexec.mainClass="my.Main" -Dexec.args="program arguments"
Snow is available from the Maven Central Repository. To include it in your own programs, add the following dependency:
<dependency>
<groupId>com.qindesign</groupId>
<artifactId>snowy-json</artifactId>
<version>0.15.0</version>
</dependency>
The linter's job is to provide suggestions about potential errors in a schema. It shows only potential problems whose presence does not necessarily mean the schema won't work.
The linter is rudimentary and does not check or validate everything about the schema. It does currently check for the following things:
format values.items arrays.additionalItems without a sibling array-form items.$schema elements inside a subschema that do not have a sibling $id.$id values.$ref values that don't exist.minLength and maxLength.exclusiveMinimum is not strictly less than exclusiveMaximum.minimum
expects that the type is "number" or "integer" and format expects a
"string" type.default and const; a type is expected to
exist and to match the implied type for these values.enums.enum, allOf, anyOf, or oneOf.minContains without a sibling contains.maxContains without a sibling contains.unevaluatedItems without a sibling array-form items.$id values that have an empty fragment.then without if.else without if.$ref members with siblings.It's possible to add your own rules to the linter. There are four important concepts to know about when adding rules:
Linter.addStringRule will execute if the current
element is a primitive string.Linter.Context.The following example snippet tests for the existence of any "anyOf" schema keywords:
JsonElement schema;
// ...load the schema...
Linter linter = new Linter();
linter.addRule(context -> {
if (context.isKeyword() && context.is("anyOf")) {
context.addIssue("anyOf detected");
}
});
Map<JSONPath, List<String>> issues = linter.check();
// ...print the issues...
The JSON class has a traverseSchema method that does a preorder tree
traversal for JSON schemas. It's what the linter uses internally. It's also
possible to use this to write your own linting rules.
The following example snippet also tests for the existence of any "anyOf" schema keywords:
JsonElement schema;
// ...load the schema...
JSON.traverseSchema(schema, (e, parent, path, state) -> {
if (!state.isNotKeyword() && path.endsWith("anyOf")) {
System.out.println(path + ": anyOf keyword present");
}
});
The coverage checker works similarly to the main validator, except that after validation, it prints out some coverage results.
It outputs two JSON objects:
There are plans to explore supporting more features, including:
ValidatorContext, i.e.
across calls to Validator.validate.These are plans that may or may not be explored:
I'd love to say this: "The validator isn't wrong, the spec is ambiguous."™
Realistic? No, but fun to say anyway.
Thanks to JetBrains for providing an Open Source licence for IntelliJ, my favourite IDE since forever.
Snow, a JSON Schema validator
Copyright (c) 2020-2021 Shawn Silverman
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Copyright (c) 2020-2021 Shawn Silverman
527 commits
Java
100.0%