A comprehensive Java implementation of the JSON Schema validation specification supporting both draft v4 and v3 with complete validation capabilities and extensibility.
—
Dedicated functionality for validating JSON Schema syntax without performing instance validation. The syntax validator ensures that schemas conform to the JSON Schema specification structure and rules before they are used for instance validation.
Standalone validator for checking JSON Schema syntax and structure.
/**
* Validator for JSON Schema syntax without instance validation
*/
public final class SyntaxValidator {
/**
* Check if schema has valid syntax
* @param schema JsonNode containing the JSON schema to validate
* @return true if schema syntax is valid, false otherwise
*/
public boolean schemaIsValid(JsonNode schema);
/**
* Validate schema syntax and return detailed report
* @param schema JsonNode containing the JSON schema to validate
* @return ProcessingReport with validation results and error details
* @throws ProcessingException if syntax validation processing fails
*/
public ProcessingReport validateSchema(JsonNode schema) throws ProcessingException;
/**
* Get underlying processor for advanced usage
* @return Processor for schema tree validation
*/
public Processor<ValueHolder<SchemaTree>, ValueHolder<SchemaTree>> getProcessor();
}Usage Examples:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.fge.jsonschema.processors.syntax.SyntaxValidator;
import com.github.fge.jsonschema.main.JsonSchemaFactory;
import com.github.fge.jsonschema.core.report.ProcessingReport;
// Get syntax validator from factory
JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
SyntaxValidator syntaxValidator = factory.getSyntaxValidator();
// Simple boolean validation
ObjectMapper mapper = new ObjectMapper();
JsonNode validSchema = mapper.readTree("{\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"name\": { \"type\": \"string\" },\n" +
" \"age\": { \"type\": \"integer\", \"minimum\": 0 }\n" +
" },\n" +
" \"required\": [\"name\"]\n" +
"}");
boolean isValid = syntaxValidator.schemaIsValid(validSchema);
System.out.println("Schema is valid: " + isValid); // true
// Detailed validation report
ProcessingReport report = syntaxValidator.validateSchema(validSchema);
if (report.isSuccess()) {
System.out.println("Schema syntax is correct");
} else {
System.out.println("Schema syntax errors:");
for (ProcessingMessage message : report) {
System.out.println(" " + message.getMessage());
}
}
// Validate invalid schema
JsonNode invalidSchema = mapper.readTree("{\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"age\": { \"type\": \"integer\", \"minimum\": \"not-a-number\" }\n" +
" }\n" +
"}");
ProcessingReport invalidReport = syntaxValidator.validateSchema(invalidSchema);
if (!invalidReport.isSuccess()) {
System.out.println("Syntax errors found:");
for (ProcessingMessage message : invalidReport) {
System.out.println(" Path: " + message.asJson().get("instance").get("pointer"));
System.out.println(" Error: " + message.getMessage());
}
}// Valid Draft v4 schema structure
{
"$schema": "http://json-schema.org/draft-04/schema#", // Optional
"type": "object", // Root type
"properties": { // Object properties
"name": { "type": "string" },
"age": { "type": "integer", "minimum": 0 }
},
"required": ["name"], // Required properties
"additionalProperties": false // Additional property control
}// Valid Draft v3 schema structure
{
"$schema": "http://json-schema.org/draft-03/schema#", // Optional
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer", "minimum": 0 }
},
"required": ["name"], // Different required syntax
"additionalProperties": false
}/**
* Validate schema syntax before using it for instance validation
*/
public JsonSchema createSchemaWithValidation(JsonNode schemaNode) throws ProcessingException {
JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
SyntaxValidator syntaxValidator = factory.getSyntaxValidator();
// Check syntax first
ProcessingReport syntaxReport = syntaxValidator.validateSchema(schemaNode);
if (!syntaxReport.isSuccess()) {
StringBuilder errors = new StringBuilder("Schema syntax errors:\n");
for (ProcessingMessage message : syntaxReport) {
errors.append(" ").append(message.getMessage()).append("\n");
}
throw new ProcessingException(errors.toString());
}
// Safe to create schema instance
return factory.getJsonSchema(schemaNode);
}/**
* Service for validating multiple schemas
*/
public class SchemaValidationService {
private final SyntaxValidator syntaxValidator;
public SchemaValidationService() {
this.syntaxValidator = JsonSchemaFactory.byDefault().getSyntaxValidator();
}
public ValidationResult validateSchemas(List<JsonNode> schemas) {
List<String> errors = new ArrayList<>();
List<JsonNode> validSchemas = new ArrayList<>();
for (int i = 0; i < schemas.size(); i++) {
JsonNode schema = schemas.get(i);
ProcessingReport report = syntaxValidator.validateSchema(schema);
if (report.isSuccess()) {
validSchemas.add(schema);
} else {
StringBuilder schemaErrors = new StringBuilder();
schemaErrors.append("Schema ").append(i).append(" errors:\n");
for (ProcessingMessage message : report) {
schemaErrors.append(" ").append(message.getMessage()).append("\n");
}
errors.add(schemaErrors.toString());
}
}
return new ValidationResult(validSchemas, errors);
}
public boolean isValidSchema(JsonNode schema) {
return syntaxValidator.schemaIsValid(schema);
}
}
public class ValidationResult {
private final List<JsonNode> validSchemas;
private final List<String> errors;
public ValidationResult(List<JsonNode> validSchemas, List<String> errors) {
this.validSchemas = validSchemas;
this.errors = errors;
}
public boolean hasErrors() {
return !errors.isEmpty();
}
public List<JsonNode> getValidSchemas() {
return validSchemas;
}
public List<String> getErrors() {
return errors;
}
}/**
* Development utility for interactive schema validation
*/
public class SchemaDebugger {
private final SyntaxValidator syntaxValidator;
private final ObjectMapper mapper;
public SchemaDebugger() {
this.syntaxValidator = JsonSchemaFactory.byDefault().getSyntaxValidator();
this.mapper = new ObjectMapper();
}
public void debugSchema(String schemaJson) {
try {
JsonNode schema = mapper.readTree(schemaJson);
System.out.println("=== Schema Syntax Validation ===");
System.out.println("Schema:");
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(schema));
System.out.println();
ProcessingReport report = syntaxValidator.validateSchema(schema);
if (report.isSuccess()) {
System.out.println("✓ Schema syntax is valid");
System.out.println("Schema version: " + detectSchemaVersion(schema));
} else {
System.out.println("✗ Schema syntax errors found:");
for (ProcessingMessage message : report) {
JsonNode messageJson = message.asJson();
String pointer = messageJson.get("instance").get("pointer").asText();
String error = message.getMessage();
System.out.println(" Location: " + (pointer.isEmpty() ? "/" : pointer));
System.out.println(" Error: " + error);
System.out.println();
}
}
} catch (Exception e) {
System.out.println("✗ Invalid JSON: " + e.getMessage());
}
}
private String detectSchemaVersion(JsonNode schema) {
JsonNode schemaUri = schema.get("$schema");
if (schemaUri != null) {
return schemaUri.asText();
}
return "Draft v4 (default)";
}
public static void main(String[] args) {
SchemaDebugger debugger = new SchemaDebugger();
// Test valid schema
String validSchema = "{\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"name\": { \"type\": \"string\" }\n" +
" }\n" +
"}";
debugger.debugSchema(validSchema);
// Test invalid schema
String invalidSchema = "{\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"age\": { \"type\": \"integer\", \"minimum\": \"invalid\" }\n" +
" }\n" +
"}";
debugger.debugSchema(invalidSchema);
}
}type must be a string or array of strings"null", "boolean", "object", "array", "number", "integer", "string"minimum, maximum must be numbersexclusiveMinimum, exclusiveMaximum must be booleans (Draft v4) or numbers (Draft v6+)multipleOf must be a positive numberminLength, maxLength must be non-negative integerspattern must be a valid regular expression stringminItems, maxItems must be non-negative integersitems must be an object or array of objectsadditionalItems must be boolean or objectproperties must be an object with schema valuespatternProperties must be an object with regex keys and schema valuesadditionalProperties must be boolean or objectrequired must be array of strings (Draft v4) or boolean (Draft v3)minProperties, maxProperties must be non-negative integers/**
* Complete validation pipeline with syntax checking
*/
public class CompleteValidationPipeline {
private final JsonSchemaFactory factory;
private final SyntaxValidator syntaxValidator;
public CompleteValidationPipeline() {
this.factory = JsonSchemaFactory.byDefault();
this.syntaxValidator = factory.getSyntaxValidator();
}
public ProcessingReport validateWithSyntaxCheck(JsonNode schema, JsonNode instance) throws ProcessingException {
// Step 1: Validate schema syntax
ProcessingReport syntaxReport = syntaxValidator.validateSchema(schema);
if (!syntaxReport.isSuccess()) {
return syntaxReport; // Return syntax errors
}
// Step 2: Create schema and validate instance
JsonSchema jsonSchema = factory.getJsonSchema(schema);
return jsonSchema.validate(instance);
}
public ValidationResult completeValidation(JsonNode schema, JsonNode instance) {
try {
ProcessingReport report = validateWithSyntaxCheck(schema, instance);
return new ValidationResult(report.isSuccess(), report);
} catch (ProcessingException e) {
return new ValidationResult(false, null, e.getMessage());
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-github-fge--json-schema-validator