This module implements a JSON Type Definition (JTD) validator based on RFC 8927. JTD is a schema language for JSON designed for code generation and portable validation with standardized error indicators. Unlike JSON Schema, JTD uses eight mutually-exclusive forms that make validation simpler and more predictable.
Key Architectural Principles:
JTD defines eight mutually-exclusive schema forms:
Discriminator schemas enforce compile-time constraints to ensure predictable validation:
nullable: trueThese constraints are enforced at compile-time, preventing invalid schemas from reaching validation.
flowchart TD
A[JSON Document] --> B[Json.parse]
B --> C[JsonValue]
C --> D{JTDSchema.compile}
D --> E[Parse Phase]
E --> F[Validation Phase]
F --> G[ValidationResult]
E --> E1[Identify Schema Form]
E --> E2[Extract Definitions]
E --> E3[Build Immutable Records]
F --> F1[Stack-based Validation]
F --> F2[Error Path Tracking]
F --> F3[Standardized Errors]
Following modern Java patterns, we use a package-private sealed interface with record implementations and a public facade class:
package json.java21.jtd;
import jdk.incubator.java.util.json.*;
/// Package-private sealed interface for schema types
sealed interface JtdSchema
permits JtdSchema.EmptySchema,
JtdSchema.RefSchema,
JtdSchema.TypeSchema,
JtdSchema.EnumSchema,
JtdSchema.ElementsSchema,
JtdSchema.PropertiesSchema,
JtdSchema.ValuesSchema,
JtdSchema.DiscriminatorSchema,
JtdSchema.NullableSchema {
/// Schema type records (package-private)
record EmptySchema() implements JtdSchema {}
record RefSchema(String ref, Map<String, JtdSchema> definitions) implements JtdSchema {}
record TypeSchema(PrimitiveType type) implements JtdSchema {}
record EnumSchema(Set<String> values) implements JtdSchema {}
record ElementsSchema(JtdSchema elements) implements JtdSchema {}
record PropertiesSchema(
Map<String, JtdSchema> properties,
Map<String, JtdSchema> optionalProperties,
boolean additionalProperties
) implements JtdSchema {}
record ValuesSchema(JtdSchema values) implements JtdSchema {}
record DiscriminatorSchema(
String discriminator,
Map<String, JtdSchema> mapping
) implements JtdSchema {}
record NullableSchema(JtdSchema nullable) implements JtdSchema {}
}
/// Public facade class for JTD operations
public class Jtd {
/// Compile and validate JSON against JTD schema
public Result validate(JsonValue schema, JsonValue instance) {
JtdSchema jtdSchema = compileSchema(schema);
return validateWithStack(jtdSchema, instance);
}
/// Validation result
public record Result(boolean isValid, List<String> errors) {}
}
JTD supports these primitive types, each with specific validation rules:
enum PrimitiveType {
BOOLEAN,
FLOAT32, FLOAT64,
INT8, UINT8, INT16, UINT16, INT32, UINT32,
STRING,
TIMESTAMP
}
Architectural Impact:
The JTD validator uses a single stack-based validation engine that enforces RFC 8927 compliance through immutable schema records. All validation flows through one path to prevent behavioral divergence.
pushChildFrames() and explicit stack traversalsequenceDiagram
participant User
participant JTD
participant ValidationStack
participant ErrorCollector
User->>JTD: validate(schemaJson, instanceJson)
JTD->>JTD: compileSchema(schemaJson)
Note over JTD: Compile-time checks enforce RFC constraints
JTD->>ValidationStack: push(rootSchema, "#")
loop While stack not empty
ValidationStack->>JTD: pop()
JTD->>JTD: validateCurrent()
alt Validation fails
JTD->>ErrorCollector: addError(path, message)
else Has children
JTD->>ValidationStack: push(children)
end
end
JTD->>User: ValidationResult
JTD specifies standardized error format with:
record ValidationError(
String instancePath, // RFC 8927 §3.2.1
String schemaPath, // RFC 8927 §3.2.2
String message // Human-readable error description
) {}
flowchart TD
A[JsonValue Schema] --> B{Identify Form}
B -->|empty| C[EmptySchema]
B -->|ref| D[RefSchema]
B -->|type| E[TypeSchema]
B -->|enum| F[EnumSchema]
B -->|elements| G[ElementsSchema]
B -->|properties| H[PropertiesSchema]
B -->|values| I[ValuesSchema]
B -->|discriminator| J[DiscriminatorSchema]
C --> K[Immutable Record]
D --> K
E --> K
F --> K
G --> K
H --> K
I --> K
J --> K
K --> L[JTDSchema Instance]
JTD allows schema definitions for reuse via $ref:
record CompiledSchema(
JTDSchema root,
Map<String, JTDSchema> definitions // RFC 8927 §2.1
) {}
Constraints (RFC 8927 §2.1.1):
| Aspect | JTD (This Module) | JSON Schema |
|---|---|---|
| Schema Forms | 8 mutually exclusive | 40+ combinable keywords |
| References | Simple $ref to definitions |
Complex $ref with URI resolution |
| Validation Logic | Exhaustive switch on sealed types | Complex boolean logic with allOf/anyOf/not |
| Error Paths | Simple instance+schema paths | Complex evaluation paths |
| Remote Schemas | Not supported | Full URI resolution |
| Type System | Fixed primitive set | Extensible validation keywords |
JTDSchema with 8 record implementationsPrimitiveType enum with validation logicValidationError and ValidationResult recordsimport jdk.incubator.java.util.json.*;
import json.java21.jtd.Jtd;
// Create JTD validator
Jtd jtd = new Jtd();
// Compile JTD schema
String schemaJson = """
{
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"age": { "type": "int32" }
},
"optionalProperties": {
"email": { "type": "string" }
}
}
""";
// Validate JSON
String json = """
{"id": "123", "name": "Alice", "age": 30, "email": "alice@example.com"}
""";
Jtd.Result result = jtd.validate(Json.parse(schemaJson), Json.parse(json));
if (!result.isValid()) {
for (var error : result.errors()) {
System.out.println(error);
}
}
Run the official JTD Test Suite:
# Run all JTD spec compliance tests
$(command -v mvnd || command -v mvn || command -v ./mvnw) test -pl json-java21-jtd -Dtest=JtdSpecIT
JtdSpecIT exercises only the published validation.json cases so coverage maps exactly to behaviour that downstream users rely on. Compilation enforcement is handled through dedicated suites:
CompilerSpecIT replays invalid_schemas.json and asserts that compilation fails with deterministic exception messages for every illegal schema.CompilerTest holds incremental unit tests for compiler internals (for example, discriminator guard scenarios) while still extending the JUL logging helper to emit INFO banners per method.Run the compiler-focused suites when evolving compile-time logic:
$(command -v mvnd || command -v mvn || command -v ./mvnw) test -pl json-java21-jtd -Dtest=CompilerSpecIT
$(command -v mvnd || command -v mvn || command -v ./mvnw) test -pl json-java21-jtd -Dtest=CompilerTest
IllegalArgumentException for invalid schemasValidationResult with errors{}empty = {}{} as an empty-object schema.{} to EmptySchema and validate everything as OK.This implementation strictly follows RFC 8927: