or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

basic-validation.mdcli.mdconfiguration.mdextensions.mdformat-validation.mdindex.mdsyntax-validation.md

extensions.mddocs/

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