0
# Custom Keywords and Extensions
1
2
Extensibility system for adding custom validation keywords, format attributes, and validation logic through the pluggable library system. The library architecture allows complete customization of validation behavior while maintaining compatibility with standard JSON Schema specifications.
3
4
## Capabilities
5
6
### Library System
7
8
Core library management for organizing keywords, digesters, validators, and format attributes.
9
10
```java { .api }
11
/**
12
* Immutable library containing validation components
13
*/
14
public final class Library {
15
/**
16
* Create new library builder
17
* @return LibraryBuilder for customization
18
*/
19
public static LibraryBuilder newBuilder();
20
21
/**
22
* Get syntax checkers for keyword syntax validation
23
* @return Dictionary of keyword name to SyntaxChecker
24
*/
25
public Dictionary<SyntaxChecker> getSyntaxCheckers();
26
27
/**
28
* Get digest processors for schema optimization
29
* @return Dictionary of keyword name to Digester
30
*/
31
public Dictionary<Digester> getDigesters();
32
33
/**
34
* Get validator constructors for keyword validation
35
* @return Dictionary of keyword name to KeywordValidator constructor
36
*/
37
public Dictionary<Constructor<? extends KeywordValidator>> getValidators();
38
39
/**
40
* Get format attributes for format validation
41
* @return Dictionary of format name to FormatAttribute
42
*/
43
public Dictionary<FormatAttribute> getFormatAttributes();
44
45
/**
46
* Create mutable copy for modification
47
* @return LibraryBuilder with current library contents
48
*/
49
public LibraryBuilder thaw();
50
}
51
52
/**
53
* Builder for creating custom libraries
54
*/
55
public final class LibraryBuilder {
56
/**
57
* Add complete keyword definition to library
58
* @param keyword Keyword instance with all components
59
* @return This builder for method chaining
60
*/
61
public LibraryBuilder addKeyword(Keyword keyword);
62
63
/**
64
* Remove keyword from library by name
65
* @param name Keyword name to remove
66
* @return This builder for method chaining
67
*/
68
public LibraryBuilder removeKeyword(String name);
69
70
/**
71
* Add format attribute for format validation
72
* @param name Format name (e.g., "email", "date-time")
73
* @param attribute FormatAttribute implementation
74
* @return This builder for method chaining
75
*/
76
public LibraryBuilder addFormatAttribute(String name, FormatAttribute attribute);
77
78
/**
79
* Remove format attribute by name
80
* @param name Format name to remove
81
* @return This builder for method chaining
82
*/
83
public LibraryBuilder removeFormatAttribute(String name);
84
85
/**
86
* Create immutable library instance
87
* @return Library with configured components
88
*/
89
public Library freeze();
90
}
91
```
92
93
### Keyword Definition
94
95
Complete keyword definition including syntax checking, digesting, and validation.
96
97
```java { .api }
98
/**
99
* Immutable keyword definition with all validation components
100
*/
101
public final class Keyword {
102
/**
103
* Create new keyword builder with name
104
* @param name Keyword name (e.g., "minLength", "pattern")
105
* @return KeywordBuilder for configuration
106
*/
107
public static KeywordBuilder newBuilder(String name);
108
109
/**
110
* Get keyword name
111
* @return String name of this keyword
112
*/
113
public String getName();
114
115
/**
116
* Create mutable copy for modification
117
* @return KeywordBuilder with current keyword settings
118
*/
119
public KeywordBuilder thaw();
120
}
121
122
/**
123
* Builder for creating custom keywords
124
*/
125
public final class KeywordBuilder {
126
/**
127
* Set syntax checker for validating keyword syntax in schemas
128
* @param syntaxChecker SyntaxChecker implementation
129
* @return This builder for method chaining
130
*/
131
public KeywordBuilder withSyntaxChecker(SyntaxChecker syntaxChecker);
132
133
/**
134
* Set custom digester for schema preprocessing
135
* @param digester Digester implementation
136
* @return This builder for method chaining
137
*/
138
public KeywordBuilder withDigester(Digester digester);
139
140
/**
141
* Set identity digester for simple pass-through processing
142
* @param first Required node type
143
* @param other Additional supported node types
144
* @return This builder for method chaining
145
*/
146
public KeywordBuilder withIdentityDigester(NodeType first, NodeType... other);
147
148
/**
149
* Set simple digester for basic keyword extraction
150
* @param first Required node type
151
* @param other Additional supported node types
152
* @return This builder for method chaining
153
*/
154
public KeywordBuilder withSimpleDigester(NodeType first, NodeType... other);
155
156
/**
157
* Set validator class for keyword validation logic
158
* @param c KeywordValidator implementation class
159
* @return This builder for method chaining
160
*/
161
public KeywordBuilder withValidatorClass(Class<? extends KeywordValidator> c);
162
163
/**
164
* Create immutable keyword instance
165
* @return Keyword with configured components
166
*/
167
public Keyword freeze();
168
}
169
```
170
171
### Custom Keyword Validator Interface
172
173
Interface for implementing custom validation logic.
174
175
```java { .api }
176
/**
177
* Interface for implementing custom keyword validation logic
178
*/
179
public interface KeywordValidator {
180
/**
181
* Validate keyword against instance data
182
* @param processor Validation processor for recursive validation
183
* @param report Processing report for collecting validation results
184
* @param bundle Message bundle for error messages
185
* @param data Full validation data including schema and instance
186
* @throws ProcessingException if validation processing fails
187
*/
188
void validate(Processor<FullData, FullData> processor, ProcessingReport report,
189
MessageBundle bundle, FullData data) throws ProcessingException;
190
}
191
192
/**
193
* Abstract base class providing common functionality for keyword validators
194
*/
195
public abstract class AbstractKeywordValidator implements KeywordValidator {
196
/**
197
* Constructor with keyword name
198
* @param keyword Name of the keyword this validator handles
199
*/
200
protected AbstractKeywordValidator(String keyword);
201
202
/**
203
* Get keyword name
204
* @return String name of handled keyword
205
*/
206
protected final String getKeyword();
207
208
/**
209
* Validate keyword with automatic error reporting
210
* @param processor Validation processor
211
* @param report Processing report
212
* @param bundle Message bundle
213
* @param data Validation data
214
* @throws ProcessingException if validation fails
215
*/
216
public final void validate(Processor<FullData, FullData> processor, ProcessingReport report,
217
MessageBundle bundle, FullData data) throws ProcessingException;
218
219
/**
220
* Implement specific validation logic for this keyword
221
* @param processor Validation processor
222
* @param report Processing report
223
* @param bundle Message bundle
224
* @param data Validation data
225
* @throws ProcessingException if validation fails
226
*/
227
protected abstract void validate(Processor<FullData, FullData> processor, ProcessingReport report,
228
MessageBundle bundle, FullData data) throws ProcessingException;
229
}
230
```
231
232
### Digester Interface
233
234
Interface for preprocessing schemas before validation.
235
236
```java { .api }
237
/**
238
* Interface for implementing schema digesters
239
*/
240
public interface Digester {
241
/**
242
* Get supported JSON node types for this digester
243
* @return EnumSet of supported NodeType values
244
*/
245
EnumSet<NodeType> supportedTypes();
246
247
/**
248
* Digest schema into optimized form
249
* @param schema JsonNode containing schema to digest
250
* @return JsonNode with digested schema representation
251
*/
252
JsonNode digest(JsonNode schema);
253
}
254
255
/**
256
* Abstract base class for digesters
257
*/
258
public abstract class AbstractDigester implements Digester {
259
/**
260
* Constructor with keyword name and supported types
261
* @param keyword Keyword name
262
* @param first Required supported type
263
* @param other Additional supported types
264
*/
265
protected AbstractDigester(String keyword, NodeType first, NodeType... other);
266
267
/**
268
* Get supported node types
269
* @return EnumSet of supported NodeType values
270
*/
271
public final EnumSet<NodeType> supportedTypes();
272
273
/**
274
* Get keyword name
275
* @return String keyword name
276
*/
277
protected final String getKeyword();
278
}
279
```
280
281
### Pre-built Libraries
282
283
Standard libraries for different JSON Schema versions.
284
285
```java { .api }
286
/**
287
* Pre-built library for JSON Schema Draft v3
288
*/
289
public final class DraftV3Library {
290
/**
291
* Get Draft v3 library instance
292
* @return Library configured for JSON Schema Draft v3
293
*/
294
public static Library get();
295
}
296
297
/**
298
* Pre-built library for JSON Schema Draft v4
299
*/
300
public final class DraftV4Library {
301
/**
302
* Get Draft v4 library instance
303
* @return Library configured for JSON Schema Draft v4
304
*/
305
public static Library get();
306
}
307
308
/**
309
* Pre-built library for JSON Schema Draft v4 HyperSchema
310
*/
311
public final class DraftV4HyperSchemaLibrary {
312
/**
313
* Get Draft v4 HyperSchema library instance
314
* @return Library configured for JSON Schema Draft v4 HyperSchema
315
*/
316
public static Library get();
317
}
318
```
319
320
## Usage Examples
321
322
### Example 1: Custom String Length Validator
323
324
```java
325
import com.github.fge.jsonschema.keyword.validator.AbstractKeywordValidator;
326
import com.github.fge.jsonschema.core.report.ProcessingReport;
327
import com.github.fge.jsonschema.processors.data.FullData;
328
329
/**
330
* Custom validator for exact string length requirement
331
*/
332
public final class ExactLengthValidator extends AbstractKeywordValidator {
333
public ExactLengthValidator() {
334
super("exactLength");
335
}
336
337
@Override
338
protected void validate(Processor<FullData, FullData> processor,
339
ProcessingReport report, MessageBundle bundle,
340
FullData data) throws ProcessingException {
341
342
JsonNode instance = data.getInstance().getNode();
343
JsonNode schema = data.getSchema().getNode();
344
345
if (!instance.isTextual()) {
346
return; // Only validate strings
347
}
348
349
int expectedLength = schema.get("exactLength").asInt();
350
int actualLength = instance.asText().length();
351
352
if (actualLength != expectedLength) {
353
ProcessingMessage message = newMsg(data, bundle, "err.exactLength")
354
.putArgument("expected", expectedLength)
355
.putArgument("actual", actualLength);
356
report.error(message);
357
}
358
}
359
}
360
361
// Create and register the custom keyword
362
Keyword exactLengthKeyword = Keyword.newBuilder("exactLength")
363
.withSyntaxChecker(new PositiveIntegerSyntaxChecker())
364
.withSimpleDigester(NodeType.STRING)
365
.withValidatorClass(ExactLengthValidator.class)
366
.freeze();
367
368
// Add to custom library
369
Library customLibrary = DraftV4Library.get().thaw()
370
.addKeyword(exactLengthKeyword)
371
.freeze();
372
373
// Use with validation configuration
374
ValidationConfiguration config = ValidationConfiguration.newBuilder()
375
.addLibrary("http://json-schema.org/draft-04/schema#", customLibrary)
376
.freeze();
377
378
JsonSchemaFactory factory = JsonSchemaFactory.newBuilder()
379
.setValidationConfiguration(config)
380
.freeze();
381
```
382
383
### Example 2: Custom Format Attribute
384
385
```java
386
import com.github.fge.jsonschema.format.AbstractFormatAttribute;
387
import com.github.fge.jsonschema.core.report.ProcessingReport;
388
import com.github.fge.jsonschema.processors.data.FullData;
389
390
/**
391
* Custom format attribute for validating credit card numbers
392
*/
393
public final class CreditCardFormatAttribute extends AbstractFormatAttribute {
394
private static final String FORMAT_NAME = "credit-card";
395
396
public CreditCardFormatAttribute() {
397
super(FORMAT_NAME, NodeType.STRING);
398
}
399
400
@Override
401
public void validate(ProcessingReport report, MessageBundle bundle,
402
FullData data) throws ProcessingException {
403
404
JsonNode instance = data.getInstance().getNode();
405
String value = instance.asText();
406
407
if (!isValidCreditCard(value)) {
408
ProcessingMessage message = newMsg(data, bundle, "err.format.creditCard")
409
.putArgument("value", value);
410
report.error(message);
411
}
412
}
413
414
private boolean isValidCreditCard(String number) {
415
// Implement Luhn algorithm or other validation
416
return number.matches("\\d{13,19}") && luhnCheck(number);
417
}
418
419
private boolean luhnCheck(String number) {
420
// Luhn algorithm implementation
421
int sum = 0;
422
boolean alternate = false;
423
for (int i = number.length() - 1; i >= 0; i--) {
424
int n = Integer.parseInt(number.substring(i, i + 1));
425
if (alternate) {
426
n *= 2;
427
if (n > 9) {
428
n = (n % 10) + 1;
429
}
430
}
431
sum += n;
432
alternate = !alternate;
433
}
434
return (sum % 10 == 0);
435
}
436
}
437
438
// Register custom format attribute
439
Library customLibrary = DraftV4Library.get().thaw()
440
.addFormatAttribute("credit-card", new CreditCardFormatAttribute())
441
.freeze();
442
443
ValidationConfiguration config = ValidationConfiguration.newBuilder()
444
.addLibrary("http://json-schema.org/draft-04/schema#", customLibrary)
445
.setUseFormat(true)
446
.freeze();
447
```
448
449
### Example 3: Custom Digester
450
451
```java
452
import com.github.fge.jsonschema.keyword.digest.AbstractDigester;
453
454
/**
455
* Custom digester for optimizing range validation keywords
456
*/
457
public final class RangeDigester extends AbstractDigester {
458
public RangeDigester() {
459
super("range", NodeType.INTEGER, NodeType.NUMBER);
460
}
461
462
@Override
463
public JsonNode digest(JsonNode schema) {
464
ObjectNode digested = FACTORY.objectNode();
465
JsonNode rangeNode = schema.get("range");
466
467
if (rangeNode.isArray() && rangeNode.size() == 2) {
468
digested.put("minimum", rangeNode.get(0));
469
digested.put("maximum", rangeNode.get(1));
470
digested.put("exclusiveMinimum", false);
471
digested.put("exclusiveMaximum", false);
472
}
473
474
return digested;
475
}
476
}
477
478
// Custom validator that works with digested schema
479
public final class RangeValidator extends AbstractKeywordValidator {
480
public RangeValidator() {
481
super("range");
482
}
483
484
@Override
485
protected void validate(Processor<FullData, FullData> processor,
486
ProcessingReport report, MessageBundle bundle,
487
FullData data) throws ProcessingException {
488
489
JsonNode instance = data.getInstance().getNode();
490
JsonNode schema = data.getSchema().getNode();
491
492
if (!instance.isNumber()) {
493
return;
494
}
495
496
double value = instance.asDouble();
497
double min = schema.get("minimum").asDouble();
498
double max = schema.get("maximum").asDouble();
499
500
if (value < min || value > max) {
501
ProcessingMessage message = newMsg(data, bundle, "err.range")
502
.putArgument("value", value)
503
.putArgument("min", min)
504
.putArgument("max", max);
505
report.error(message);
506
}
507
}
508
}
509
510
// Create keyword with custom digester
511
Keyword rangeKeyword = Keyword.newBuilder("range")
512
.withSyntaxChecker(new ArrayOfTwoNumbersSyntaxChecker())
513
.withDigester(new RangeDigester())
514
.withValidatorClass(RangeValidator.class)
515
.freeze();
516
```
517
518
### Example 4: Complete Custom Library
519
520
```java
521
// Build comprehensive custom library
522
Library customLibrary = Library.newBuilder()
523
// Add standard Draft v4 keywords
524
.addKeyword(exactLengthKeyword)
525
.addKeyword(rangeKeyword)
526
527
// Add custom format attributes
528
.addFormatAttribute("credit-card", new CreditCardFormatAttribute())
529
.addFormatAttribute("phone-number", new PhoneNumberFormatAttribute())
530
531
// Base on existing library and modify
532
.freeze();
533
534
// Or extend existing library
535
Library extendedLibrary = DraftV4Library.get().thaw()
536
.addKeyword(exactLengthKeyword)
537
.addFormatAttribute("credit-card", new CreditCardFormatAttribute())
538
.removeKeyword("deprecated-keyword") // Remove unwanted keywords
539
.freeze();
540
541
// Use in configuration
542
ValidationConfiguration config = ValidationConfiguration.newBuilder()
543
.setDefaultLibrary("http://json-schema.org/draft-04/schema#", extendedLibrary)
544
.setUseFormat(true)
545
.freeze();
546
547
JsonSchemaFactory factory = JsonSchemaFactory.newBuilder()
548
.setValidationConfiguration(config)
549
.freeze();
550
551
// Schema can now use custom keywords
552
String schemaString = "{\n" +
553
" \"type\": \"object\",\n" +
554
" \"properties\": {\n" +
555
" \"username\": {\n" +
556
" \"type\": \"string\",\n" +
557
" \"exactLength\": 8\n" +
558
" },\n" +
559
" \"creditCard\": {\n" +
560
" \"type\": \"string\",\n" +
561
" \"format\": \"credit-card\"\n" +
562
" },\n" +
563
" \"score\": {\n" +
564
" \"type\": \"number\",\n" +
565
" \"range\": [0, 100]\n" +
566
" }\n" +
567
" }\n" +
568
"}";
569
```
570
571
## Extension Best Practices
572
573
1. **Validation Logic**: Keep validators focused on single responsibility
574
2. **Error Messages**: Provide clear, actionable error messages with relevant context
575
3. **Type Safety**: Use appropriate NodeType constraints in digesters and validators
576
4. **Performance**: Consider using digesters to optimize schema processing
577
5. **Reusability**: Design keywords to be composable and reusable across schemas
578
6. **Testing**: Thoroughly test custom extensions with various input scenarios
579
7. **Documentation**: Document custom keywords and their expected schema syntax