|
| 1 | +# JSON Type Definition (JTD) Validator Architecture |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +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. |
| 6 | + |
| 7 | +**Key Architectural Principles:** |
| 8 | +- **Simpler than JSON Schema**: Eight mutually-exclusive forms vs. complex combinatorial logic |
| 9 | +- **Immutable Design**: All schema types are records, validation is pure functions |
| 10 | +- **Stack-based Validation**: Explicit validation stack for error path tracking |
| 11 | +- **RFC 8927 Compliance**: Strict adherence to the specification |
| 12 | +- **Performance Focused**: Minimal allocations, efficient validation paths |
| 13 | + |
| 14 | +## JTD Schema Forms (RFC 8927 Section 2.2) |
| 15 | + |
| 16 | +JTD defines eight mutually-exclusive schema forms: |
| 17 | + |
| 18 | +1. **empty** - Validates any JSON value (RFC 8927 §2.2.1) |
| 19 | +2. **ref** - References a definition in the schema (RFC 8927 §2.2.2) |
| 20 | +3. **type** - Validates primitive types (RFC 8927 §2.2.3) |
| 21 | +4. **enum** - Validates against a set of string values (RFC 8927 §2.2.4) |
| 22 | +5. **elements** - Validates homogeneous arrays (RFC 8927 §2.2.5) |
| 23 | +6. **properties** - Validates objects with required/optional fields (RFC 8927 §2.2.6) |
| 24 | +7. **values** - Validates objects with homogeneous values (RFC 8927 §2.2.7) |
| 25 | +8. **discriminator** - Validates tagged unions (RFC 8927 §2.2.8) |
| 26 | + |
| 27 | +## Architecture Flow |
| 28 | + |
| 29 | +```mermaid |
| 30 | +flowchart TD |
| 31 | + A[JSON Document] --> B[Json.parse] |
| 32 | + B --> C[JsonValue] |
| 33 | + C --> D{JTDSchema.compile} |
| 34 | + D --> E[Parse Phase] |
| 35 | + E --> F[Validation Phase] |
| 36 | + F --> G[ValidationResult] |
| 37 | + |
| 38 | + E --> E1[Identify Schema Form] |
| 39 | + E --> E2[Extract Definitions] |
| 40 | + E --> E3[Build Immutable Records] |
| 41 | + |
| 42 | + F --> F1[Stack-based Validation] |
| 43 | + F --> F2[Error Path Tracking] |
| 44 | + F --> F3[Standardized Errors] |
| 45 | +``` |
| 46 | + |
| 47 | +## Core API Design |
| 48 | + |
| 49 | +Following the pattern from json-java21-schema, we use a single public sealed interface with package-private record implementations: |
| 50 | + |
| 51 | +```java |
| 52 | +package io.github.simbo1905.json.jtd; |
| 53 | + |
| 54 | +import jdk.sandbox.java.util.json.*; |
| 55 | + |
| 56 | +public sealed interface JTDSchema |
| 57 | + permits JTDSchema.EmptySchema, |
| 58 | + JTDSchema.RefSchema, |
| 59 | + JTDSchema.TypeSchema, |
| 60 | + JTDSchema.EnumSchema, |
| 61 | + JTDSchema.ElementsSchema, |
| 62 | + JTDSchema.PropertiesSchema, |
| 63 | + JTDSchema.ValuesSchema, |
| 64 | + JTDSchema.DiscriminatorSchema { |
| 65 | + |
| 66 | + /// Compile JTD schema from JSON |
| 67 | + static JTDSchema compile(JsonValue schemaJson) { |
| 68 | + // Parse and build immutable schema hierarchy |
| 69 | + } |
| 70 | + |
| 71 | + /// Validate JSON document against schema |
| 72 | + default ValidationResult validate(JsonValue json) { |
| 73 | + // Stack-based validation |
| 74 | + } |
| 75 | + |
| 76 | + /// Schema type records (package-private) |
| 77 | + record EmptySchema() implements JTDSchema {} |
| 78 | + record RefSchema(String ref) implements JTDSchema {} |
| 79 | + record TypeSchema(PrimitiveType type) implements JTDSchema {} |
| 80 | + record EnumSchema(Set<String> values) implements JTDSchema {} |
| 81 | + record ElementsSchema(JTDSchema elements) implements JTDSchema {} |
| 82 | + record PropertiesSchema( |
| 83 | + Map<String, JTDSchema> properties, |
| 84 | + Map<String, JTDSchema> optionalProperties, |
| 85 | + boolean additionalProperties |
| 86 | + ) implements JTDSchema {} |
| 87 | + record ValuesSchema(JTDSchema values) implements JTDSchema {} |
| 88 | + record DiscriminatorSchema( |
| 89 | + String discriminator, |
| 90 | + Map<String, JTDSchema> mapping |
| 91 | + ) implements JTDSchema {} |
| 92 | + |
| 93 | + /// Validation result |
| 94 | + record ValidationResult(boolean valid, List<ValidationError> errors) {} |
| 95 | + record ValidationError(String instancePath, String schemaPath, String message) {} |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +## Type System (RFC 8927 Section 2.2.3) |
| 100 | + |
| 101 | +JTD supports these primitive types, each with specific validation rules: |
| 102 | + |
| 103 | +```java |
| 104 | +enum PrimitiveType { |
| 105 | + BOOLEAN, |
| 106 | + FLOAT32, FLOAT64, |
| 107 | + INT8, UINT8, INT16, UINT16, INT32, UINT32, |
| 108 | + STRING, |
| 109 | + TIMESTAMP |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +**Architectural Impact:** |
| 114 | +- **No 64-bit integers** (RFC 8927 §2.2.3.1): Simplifies numeric validation |
| 115 | +- **Timestamp format** (RFC 8927 §2.2.3.2): Must be RFC 3339 format |
| 116 | +- **Float precision** (RFC 8927 §2.2.3.3): Separate validation for 32-bit vs 64-bit |
| 117 | + |
| 118 | +## Validation Architecture |
| 119 | + |
| 120 | +```mermaid |
| 121 | +sequenceDiagram |
| 122 | + participant User |
| 123 | + participant JTDSchema |
| 124 | + participant ValidationStack |
| 125 | + participant ErrorCollector |
| 126 | + |
| 127 | + User->>JTDSchema: validate(json) |
| 128 | + JTDSchema->>ValidationStack: push(rootSchema, "#") |
| 129 | + loop While stack not empty |
| 130 | + ValidationStack->>JTDSchema: pop() |
| 131 | + JTDSchema->>JTDSchema: validateCurrent() |
| 132 | + alt Validation fails |
| 133 | + JTDSchema->>ErrorCollector: addError(path, message) |
| 134 | + else Has children |
| 135 | + JTDSchema->>ValidationStack: push(children) |
| 136 | + end |
| 137 | + end |
| 138 | + JTDSchema->>User: ValidationResult |
| 139 | +``` |
| 140 | + |
| 141 | +## Error Reporting (RFC 8927 Section 3.2) |
| 142 | + |
| 143 | +JTD specifies standardized error format with: |
| 144 | +- **instancePath**: JSON Pointer to failing value in instance |
| 145 | +- **schemaPath**: JSON Pointer to failing constraint in schema |
| 146 | + |
| 147 | +```java |
| 148 | +record ValidationError( |
| 149 | + String instancePath, // RFC 8927 §3.2.1 |
| 150 | + String schemaPath, // RFC 8927 §3.2.2 |
| 151 | + String message // Human-readable error description |
| 152 | +) {} |
| 153 | +``` |
| 154 | + |
| 155 | +## Compilation Phase |
| 156 | + |
| 157 | +```mermaid |
| 158 | +flowchart TD |
| 159 | + A[JsonValue Schema] --> B{Identify Form} |
| 160 | + B -->|empty| C[EmptySchema] |
| 161 | + B -->|ref| D[RefSchema] |
| 162 | + B -->|type| E[TypeSchema] |
| 163 | + B -->|enum| F[EnumSchema] |
| 164 | + B -->|elements| G[ElementsSchema] |
| 165 | + B -->|properties| H[PropertiesSchema] |
| 166 | + B -->|values| I[ValuesSchema] |
| 167 | + B -->|discriminator| J[DiscriminatorSchema] |
| 168 | + |
| 169 | + C --> K[Immutable Record] |
| 170 | + D --> K |
| 171 | + E --> K |
| 172 | + F --> K |
| 173 | + G --> K |
| 174 | + H --> K |
| 175 | + I --> K |
| 176 | + J --> K |
| 177 | + |
| 178 | + K --> L[JTDSchema Instance] |
| 179 | +``` |
| 180 | + |
| 181 | +## Definitions Support (RFC 8927 Section 2.1) |
| 182 | + |
| 183 | +JTD allows schema definitions for reuse via `$ref`: |
| 184 | + |
| 185 | +```java |
| 186 | +record CompiledSchema( |
| 187 | + JTDSchema root, |
| 188 | + Map<String, JTDSchema> definitions // RFC 8927 §2.1 |
| 189 | +) {} |
| 190 | +``` |
| 191 | + |
| 192 | +**Constraints** (RFC 8927 §2.1.1): |
| 193 | +- Definitions cannot be nested |
| 194 | +- Only top-level definitions allowed |
| 195 | +- References must resolve to defined schemas |
| 196 | + |
| 197 | +## Simplifications vs JSON Schema |
| 198 | + |
| 199 | +| Aspect | JTD (This Module) | JSON Schema (json-java21-schema) | |
| 200 | +|--------|-------------------|-----------------------------------| |
| 201 | +| Schema Forms | 8 mutually exclusive | 40+ combinable keywords | |
| 202 | +| References | Simple `$ref` to definitions | Complex `$ref` with URI resolution | |
| 203 | +| Validation Logic | Exhaustive switch on sealed types | Complex boolean logic with allOf/anyOf/not | |
| 204 | +| Error Paths | Simple instance+schema paths | Complex evaluation paths | |
| 205 | +| Remote Schemas | Not supported | Full URI resolution | |
| 206 | +| Type System | Fixed primitive set | Extensible validation keywords | |
| 207 | + |
| 208 | +## Implementation Strategy |
| 209 | + |
| 210 | +### Phase 1: Core Types |
| 211 | +1. Define sealed interface `JTDSchema` with 8 record implementations |
| 212 | +2. Implement `PrimitiveType` enum with validation logic |
| 213 | +3. Create `ValidationError` and `ValidationResult` records |
| 214 | + |
| 215 | +### Phase 2: Parser |
| 216 | +1. Implement schema form detection (mutually exclusive check) |
| 217 | +2. Build immutable record hierarchy from JSON |
| 218 | +3. Handle definitions extraction and validation |
| 219 | + |
| 220 | +### Phase 3: Validator |
| 221 | +1. Implement stack-based validation engine |
| 222 | +2. Add error path tracking (instance + schema paths) |
| 223 | +3. Implement all 8 schema form validators |
| 224 | + |
| 225 | +### Phase 4: Testing |
| 226 | +1. Unit tests for each schema form |
| 227 | +2. Integration tests with RFC examples |
| 228 | +3. Error case validation |
| 229 | +4. Performance benchmarks |
| 230 | + |
| 231 | +## Usage Example |
| 232 | + |
| 233 | +```java |
| 234 | +import jdk.sandbox.java.util.json.*; |
| 235 | +import io.github.simbo1905.json.jtd.JTDSchema; |
| 236 | + |
| 237 | +// Compile JTD schema |
| 238 | +String schemaJson = """ |
| 239 | +{ |
| 240 | + "properties": { |
| 241 | + "id": { "type": "string" }, |
| 242 | + "name": { "type": "string" }, |
| 243 | + "age": { "type": "int32" } |
| 244 | + }, |
| 245 | + "optionalProperties": { |
| 246 | + "email": { "type": "string" } |
| 247 | + } |
| 248 | +} |
| 249 | +"""; |
| 250 | + |
| 251 | +JTDSchema schema = JTDSchema.compile(Json.parse(schemaJson)); |
| 252 | + |
| 253 | +// Validate JSON |
| 254 | +String json = """ |
| 255 | +{"id": "123", "name": "Alice", "age": 30, "email": "alice@example.com"} |
| 256 | +"""; |
| 257 | + |
| 258 | +JTDSchema.ValidationResult result = schema.validate(Json.parse(json)); |
| 259 | + |
| 260 | +if (!result.valid()) { |
| 261 | + for (var error : result.errors()) { |
| 262 | + System.out.println(error.instancePath() + ": " + error.message()); |
| 263 | + } |
| 264 | +} |
| 265 | +``` |
| 266 | + |
| 267 | +## Performance Considerations |
| 268 | + |
| 269 | +1. **Immutable Records**: Zero mutation during validation |
| 270 | +2. **Stack-based Validation**: Explicit stack vs recursion prevents StackOverflowError |
| 271 | +3. **Minimal Allocations**: Reuse validation context objects |
| 272 | +4. **Early Exit**: Fail fast on first validation error (when appropriate) |
| 273 | +5. **Type-specific Validation**: Optimized paths for each primitive type |
| 274 | + |
| 275 | +## Error Handling |
| 276 | + |
| 277 | +- **Schema Compilation**: `IllegalArgumentException` for invalid schemas |
| 278 | +- **Validation**: Never throws, returns `ValidationResult` with errors |
| 279 | +- **Definitions**: Validate all definitions exist at compile time |
| 280 | +- **Type Checking**: Strict RFC 8927 compliance for all primitive types |
| 281 | + |
| 282 | +## RFC 8927 Compliance |
| 283 | + |
| 284 | +This implementation strictly follows RFC 8927: |
| 285 | +- ✅ Eight mutually-exclusive schema forms |
| 286 | +- ✅ Standardized error format (instancePath, schemaPath) |
| 287 | +- ✅ Primitive type validation (no 64-bit integers) |
| 288 | +- ✅ Definition support (non-nested) |
| 289 | +- ✅ Timestamp format validation (RFC 3339) |
| 290 | +- ✅ No remote schema support (simplification by design) |
| 291 | + |
| 292 | +## Future Extensions |
| 293 | + |
| 294 | +Potential future additions (non-RFC compliant): |
| 295 | +- Custom type validators |
| 296 | +- Additional format validators |
| 297 | +- Remote definition support |
| 298 | +- Performance optimizations for specific use cases |
0 commit comments