0
# Validator Creation
1
2
Advanced validator creation and extension capabilities for building custom validators, extending existing ones, and registering new schema versions. These functions provide the foundation for creating specialized validation logic.
3
4
## Capabilities
5
6
### Creating Custom Validators
7
8
Build completely new validator classes with custom validation logic and meta-schemas.
9
10
```python { .api }
11
def create(meta_schema,
12
validators=(),
13
version=None,
14
type_checker=None,
15
format_checker=None,
16
id_of=None,
17
applicable_validators=None):
18
"""
19
Create a new validator class.
20
21
Parameters:
22
- meta_schema: Schema that describes valid schemas for this validator
23
- validators: Mapping of keyword names to validation functions
24
- version: Version identifier for this validator
25
- type_checker: TypeChecker instance for type validation
26
- format_checker: FormatChecker instance for format validation
27
- id_of: Function to extract schema ID from schema
28
- applicable_validators: Function to filter applicable validators
29
30
Returns:
31
- type[Validator]: New validator class
32
"""
33
```
34
35
### Extending Existing Validators
36
37
Create new validator classes by extending existing ones with additional or modified validation logic.
38
39
```python { .api }
40
def extend(validator,
41
validators=(),
42
version=None,
43
type_checker=None,
44
format_checker=None):
45
"""
46
Create a new validator class by extending an existing one.
47
48
Parameters:
49
- validator: Existing validator class to extend
50
- validators: Additional or replacement validation functions
51
- version: Version identifier for the new validator
52
- type_checker: TypeChecker to use (default: inherit from base)
53
- format_checker: FormatChecker to use (default: inherit from base)
54
55
Returns:
56
- type[Validator]: Extended validator class
57
"""
58
```
59
60
### Validator Registration
61
62
Register validators for automatic selection based on schema version.
63
64
```python { .api }
65
def validates(version):
66
"""
67
Decorator to register a validator for a schema version.
68
69
Parameters:
70
- version: Version identifier string
71
72
Returns:
73
- callable: Class decorator for validator registration
74
"""
75
```
76
77
### Validation Function Signature
78
79
All validation functions must follow this signature:
80
81
```python { .api }
82
def validation_function(validator, value, instance, schema):
83
"""
84
Custom validation function.
85
86
Parameters:
87
- validator: The validator instance
88
- value: The schema value for this keyword
89
- instance: The instance being validated
90
- schema: The complete schema being validated against
91
92
Yields:
93
- ValidationError: Each validation error found
94
"""
95
```
96
97
## Usage Examples
98
99
### Creating a Simple Custom Validator
100
101
```python
102
from jsonschema import create, ValidationError, Draft202012Validator
103
from jsonschema._types import draft202012_type_checker
104
from jsonschema._format import draft202012_format_checker
105
106
def even_number(validator, value, instance, schema):
107
"""Validate that a number is even."""
108
if not validator.is_type(instance, "number"):
109
return
110
111
if value and instance % 2 != 0:
112
yield ValidationError(f"{instance} is not an even number")
113
114
def minimum_length(validator, value, instance, schema):
115
"""Validate minimum string length."""
116
if not validator.is_type(instance, "string"):
117
return
118
119
if len(instance) < value:
120
yield ValidationError(f"String too short: {len(instance)} < {value}")
121
122
# Create custom validator
123
CustomValidator = create(
124
meta_schema=Draft202012Validator.META_SCHEMA,
125
validators={
126
"evenNumber": even_number,
127
"minimumLength": minimum_length,
128
# Include standard validators
129
**Draft202012Validator.VALIDATORS
130
},
131
type_checker=draft202012_type_checker,
132
format_checker=draft202012_format_checker,
133
version="custom-v1"
134
)
135
136
# Use custom validator
137
schema = {
138
"type": "object",
139
"properties": {
140
"count": {"type": "number", "evenNumber": True},
141
"name": {"type": "string", "minimumLength": 3}
142
}
143
}
144
145
validator = CustomValidator(schema)
146
147
# Test validation
148
valid_data = {"count": 4, "name": "Alice"}
149
validator.validate(valid_data) # Passes
150
151
invalid_data = {"count": 3, "name": "Al"} # Odd number, short name
152
errors = list(validator.iter_errors(invalid_data))
153
for error in errors:
154
print(f"Custom validation error: {error.message}")
155
```
156
157
### Extending Existing Validators
158
159
```python
160
from jsonschema import extend, Draft202012Validator, ValidationError
161
162
def divisible_by(validator, value, instance, schema):
163
"""Validate that a number is divisible by the given value."""
164
if not validator.is_type(instance, "number"):
165
return
166
167
if instance % value != 0:
168
yield ValidationError(f"{instance} is not divisible by {value}")
169
170
def contains_word(validator, value, instance, schema):
171
"""Validate that a string contains a specific word."""
172
if not validator.is_type(instance, "string"):
173
return
174
175
if value not in instance:
176
yield ValidationError(f"String does not contain required word: {value}")
177
178
# Extend Draft 2020-12 validator
179
ExtendedValidator = extend(
180
Draft202012Validator,
181
validators={
182
"divisibleBy": divisible_by,
183
"containsWord": contains_word
184
},
185
version="extended-draft2020-12"
186
)
187
188
# Use extended validator
189
schema = {
190
"type": "object",
191
"properties": {
192
"score": {"type": "number", "divisibleBy": 5},
193
"description": {"type": "string", "containsWord": "python"}
194
}
195
}
196
197
validator = ExtendedValidator(schema)
198
199
valid_data = {"score": 85, "description": "I love python programming"}
200
validator.validate(valid_data) # Passes
201
202
invalid_data = {"score": 87, "description": "I love javascript"}
203
errors = list(validator.iter_errors(invalid_data))
204
for error in errors:
205
print(f"Extended validation error: {error.message}")
206
```
207
208
### Registering Custom Validators
209
210
```python
211
from jsonschema import validates, create, validator_for
212
from jsonschema._types import draft202012_type_checker
213
from jsonschema._format import draft202012_format_checker
214
215
# Custom meta-schema
216
CUSTOM_META_SCHEMA = {
217
"$schema": "https://json-schema.org/draft/2020-12/schema",
218
"$id": "https://example.com/custom-schema",
219
"type": "object",
220
"properties": {
221
"type": {"type": "string"},
222
"customValidation": {"type": "boolean"}
223
}
224
}
225
226
def custom_validation(validator, value, instance, schema):
227
"""Custom validation logic."""
228
if value:
229
# Perform custom validation
230
if not isinstance(instance, str) or len(instance) < 5:
231
yield ValidationError("Custom validation failed")
232
233
@validates("custom-v1")
234
class CustomValidator:
235
META_SCHEMA = CUSTOM_META_SCHEMA
236
VALIDATORS = {"customValidation": custom_validation}
237
TYPE_CHECKER = draft202012_type_checker
238
FORMAT_CHECKER = draft202012_format_checker
239
240
def __init__(self, schema, **kwargs):
241
# Implementation similar to standard validators
242
pass
243
244
# Now validator_for will automatically select CustomValidator
245
schema_with_custom_version = {
246
"$schema": "https://example.com/custom-schema",
247
"type": "string",
248
"customValidation": True
249
}
250
251
ValidatorClass = validator_for(schema_with_custom_version)
252
print(ValidatorClass.__name__) # CustomValidator
253
```
254
255
### Complex Validation Functions
256
257
```python
258
from jsonschema import ValidationError
259
260
def unique_properties(validator, value, instance, schema):
261
"""
262
Validate that all property values in an object are unique.
263
"""
264
if not validator.is_type(instance, "object"):
265
return
266
267
if not value: # Skip if not enabled
268
return
269
270
values = list(instance.values())
271
seen = set()
272
duplicates = set()
273
274
for val in values:
275
# Only check hashable values
276
try:
277
if val in seen:
278
duplicates.add(val)
279
else:
280
seen.add(val)
281
except TypeError:
282
# Skip unhashable values
283
continue
284
285
if duplicates:
286
yield ValidationError(
287
f"Object has duplicate values: {duplicates}",
288
validator="uniqueProperties",
289
validator_value=value,
290
instance=instance,
291
schema=schema
292
)
293
294
def conditional_required(validator, value, instance, schema):
295
"""
296
Conditionally require properties based on other property values.
297
298
Example: {"ifProperty": "type", "equals": "user", "thenRequired": ["email"]}
299
"""
300
if not validator.is_type(instance, "object"):
301
return
302
303
if_prop = value.get("ifProperty")
304
equals = value.get("equals")
305
then_required = value.get("thenRequired", [])
306
307
if if_prop in instance and instance[if_prop] == equals:
308
for required_prop in then_required:
309
if required_prop not in instance:
310
yield ValidationError(
311
f"Property '{required_prop}' is required when '{if_prop}' equals '{equals}'",
312
validator="conditionalRequired",
313
validator_value=value,
314
instance=instance,
315
schema=schema,
316
path=[required_prop]
317
)
318
319
# Use complex validators
320
AdvancedValidator = extend(
321
Draft202012Validator,
322
validators={
323
"uniqueProperties": unique_properties,
324
"conditionalRequired": conditional_required
325
}
326
)
327
328
schema = {
329
"type": "object",
330
"uniqueProperties": True,
331
"conditionalRequired": {
332
"ifProperty": "type",
333
"equals": "user",
334
"thenRequired": ["email", "username"]
335
},
336
"properties": {
337
"type": {"type": "string"},
338
"email": {"type": "string"},
339
"username": {"type": "string"},
340
"name": {"type": "string"},
341
"age": {"type": "number"}
342
}
343
}
344
345
validator = AdvancedValidator(schema)
346
347
# Valid data
348
valid_data = {
349
"type": "user",
350
"email": "user@example.com",
351
"username": "john_doe",
352
"name": "John Doe",
353
"age": 30
354
}
355
validator.validate(valid_data) # Passes
356
357
# Invalid - duplicate values
358
invalid_duplicate = {
359
"name": "John",
360
"username": "John", # Duplicate value
361
"type": "admin"
362
}
363
364
# Invalid - missing required when type=user
365
invalid_missing = {
366
"type": "user",
367
"name": "John" # Missing email and username
368
}
369
370
for data in [invalid_duplicate, invalid_missing]:
371
errors = list(validator.iter_errors(data))
372
for error in errors:
373
print(f"Advanced validation error: {error.message}")
374
```
375
376
### Custom Meta-Schema Validation
377
378
```python
379
from jsonschema import create, ValidationError, SchemaError
380
381
# Define custom meta-schema
382
CUSTOM_META_SCHEMA = {
383
"$schema": "https://json-schema.org/draft/2020-12/schema",
384
"$id": "https://example.com/custom-meta-schema",
385
"type": "object",
386
"properties": {
387
"type": {"type": "string"},
388
"customKeyword": {"type": "string", "enum": ["strict", "loose"]}
389
},
390
"additionalProperties": False
391
}
392
393
def custom_keyword_validator(validator, value, instance, schema):
394
"""Custom keyword validation."""
395
if value == "strict" and isinstance(instance, str) and len(instance) < 10:
396
yield ValidationError("Strict mode requires strings of at least 10 characters")
397
398
CustomValidator = create(
399
meta_schema=CUSTOM_META_SCHEMA,
400
validators={"customKeyword": custom_keyword_validator}
401
)
402
403
# Valid schema according to custom meta-schema
404
valid_schema = {
405
"type": "string",
406
"customKeyword": "strict"
407
}
408
409
# Invalid schema - violates meta-schema
410
invalid_schema = {
411
"type": "string",
412
"customKeyword": "invalid_value", # Not in enum
413
"additionalProperty": "not_allowed" # Not allowed by meta-schema
414
}
415
416
try:
417
CustomValidator.check_schema(valid_schema)
418
print("Schema is valid")
419
except SchemaError as e:
420
print(f"Schema error: {e.message}")
421
422
try:
423
CustomValidator.check_schema(invalid_schema)
424
except SchemaError as e:
425
print(f"Schema validation failed: {e.message}")
426
```