An implementation of JSON Schema validation for Python
—
Customizable type checking system for JSON Schema types with built-in type checkers for each draft version and extensibility for custom type definitions. The type system maps JSON Schema types to Python types and validation logic.
The main class for JSON Schema type validation, supporting both built-in and custom type definitions.
class TypeChecker:
"""
A type property checker for JSON Schema type keyword validation.
Converts between JSON Schema types and Python types/objects.
Supports customization and extension of type checking behavior.
"""
def __init__(self, type_checkers=()):
"""
Initialize type checker.
Parameters:
- type_checkers: Mapping of type names to checker functions
"""
def is_type(self, instance, type):
"""
Check if instance is of the specified JSON Schema type.
Parameters:
- instance: Value to check
- type: JSON Schema type name
Returns:
- bool: True if instance is of the specified type
Raises:
- UndefinedTypeCheck: If type is not defined
"""
def redefine(self, type, fn):
"""
Create new TypeChecker with redefined type.
Parameters:
- type: Type name to redefine
- fn: Type checking function
Returns:
- TypeChecker: New TypeChecker instance
"""
def redefine_many(self, definitions=()):
"""
Create new TypeChecker with multiple redefined types.
Parameters:
- definitions: Dictionary mapping type names to checker functions
Returns:
- TypeChecker: New TypeChecker instance
"""
def remove(self, *types):
"""
Create new TypeChecker without specified types.
Parameters:
- types: Type names to remove
Returns:
- TypeChecker: New TypeChecker instance
Raises:
- UndefinedTypeCheck: If any type is not defined
"""Pre-configured type checkers for each JSON Schema draft version.
# Type checkers for each draft
draft202012_type_checker: TypeChecker
draft201909_type_checker: TypeChecker
draft7_type_checker: TypeChecker
draft6_type_checker: TypeChecker
draft4_type_checker: TypeChecker
draft3_type_checker: TypeCheckerIndividual type checking functions for standard JSON Schema types.
def is_array(checker, instance):
"""
Check if instance is an array (Python list).
Parameters:
- checker: TypeChecker instance
- instance: Value to check
Returns:
- bool: True if instance is a list
"""
def is_bool(checker, instance):
"""
Check if instance is a boolean.
Parameters:
- checker: TypeChecker instance
- instance: Value to check
Returns:
- bool: True if instance is a boolean
"""
def is_integer(checker, instance):
"""
Check if instance is an integer.
Parameters:
- checker: TypeChecker instance
- instance: Value to check
Returns:
- bool: True if instance is an integer (excludes booleans)
"""
def is_null(checker, instance):
"""
Check if instance is null (Python None).
Parameters:
- checker: TypeChecker instance
- instance: Value to check
Returns:
- bool: True if instance is None
"""
def is_number(checker, instance):
"""
Check if instance is a number.
Parameters:
- checker: TypeChecker instance
- instance: Value to check
Returns:
- bool: True if instance is numeric (excludes booleans)
"""
def is_object(checker, instance):
"""
Check if instance is an object (Python dict).
Parameters:
- checker: TypeChecker instance
- instance: Value to check
Returns:
- bool: True if instance is a dictionary
"""
def is_string(checker, instance):
"""
Check if instance is a string.
Parameters:
- checker: TypeChecker instance
- instance: Value to check
Returns:
- bool: True if instance is a string
"""
def is_any(checker, instance):
"""
Check if instance is any type (always returns True).
Used for Draft 3 "any" type.
Parameters:
- checker: TypeChecker instance
- instance: Value to check
Returns:
- bool: Always True
"""from jsonschema import Draft202012Validator
from jsonschema._types import draft202012_type_checker
# Direct type checking
checker = draft202012_type_checker
print(checker.is_type("hello", "string")) # True
print(checker.is_type(123, "integer")) # True
print(checker.is_type(123.5, "number")) # True
print(checker.is_type([1, 2, 3], "array")) # True
print(checker.is_type({"a": 1}, "object")) # True
print(checker.is_type(None, "null")) # True
print(checker.is_type(True, "boolean")) # True
# Type checking in validation
schema = {"type": "integer"}
validator = Draft202012Validator(schema)
validator.validate(42) # Valid
validator.validate(42.0) # Valid (Draft 6+ allows integers as floats)
validator.validate("42") # Invalid - string, not integerfrom jsonschema import Draft202012Validator, create
from jsonschema._types import TypeChecker, draft202012_type_checker
import decimal
# Define custom type checker function
def is_decimal(checker, instance):
"""Check if instance is a decimal number."""
return isinstance(instance, decimal.Decimal)
def is_positive_number(checker, instance):
"""Check if instance is a positive number."""
return (isinstance(instance, (int, float)) and
not isinstance(instance, bool) and
instance > 0)
# Create custom type checker
custom_type_checker = draft202012_type_checker.redefine_many({
"decimal": is_decimal,
"positive-number": is_positive_number
})
# Create validator with custom types
validator_class = create(
meta_schema=Draft202012Validator.META_SCHEMA,
validators=Draft202012Validator.VALIDATORS,
type_checker=custom_type_checker
)
# Use custom types in schema
schema = {
"type": "object",
"properties": {
"price": {"type": "decimal"},
"quantity": {"type": "positive-number"}
}
}
validator = validator_class(schema)
# Test custom types
import decimal
test_data = {
"price": decimal.Decimal("19.99"),
"quantity": 5
}
validator.validate(test_data) # Should pass
# Invalid data
invalid_data = {
"price": 19.99, # float, not Decimal
"quantity": -1 # not positive
}
errors = list(validator.iter_errors(invalid_data))
for error in errors:
print(f"Type error: {error.message}")from jsonschema._types import draft202012_type_checker
from collections import namedtuple
# Define custom object type that includes namedtuples
def is_object_or_namedtuple(checker, instance):
"""Check if instance is dict or namedtuple."""
return (isinstance(instance, dict) or
(hasattr(instance, '_fields') and
hasattr(instance, '_asdict')))
# Extend type checker
extended_checker = draft202012_type_checker.redefine(
"object", is_object_or_namedtuple
)
# Test with namedtuple
Person = namedtuple('Person', ['name', 'age'])
person = Person('Alice', 30)
print(extended_checker.is_type(person, "object")) # True
print(draft202012_type_checker.is_type(person, "object")) # Falsefrom jsonschema._types import draft202012_type_checker
# Create type checker without null support
no_null_checker = draft202012_type_checker.remove("null")
try:
no_null_checker.is_type(None, "null")
except UndefinedTypeCheck as e:
print(f"Type undefined: {e}")
# Available types
print("Available types:", list(no_null_checker._type_checkers.keys()))from jsonschema._types import (
draft3_type_checker,
draft4_type_checker,
draft6_type_checker
)
# Draft 3 supports "any" type
print("Draft 3 types:", list(draft3_type_checker._type_checkers.keys()))
# Includes: 'any', 'array', 'boolean', 'integer', 'null', 'number', 'object', 'string'
# Draft 4+ removes "any" type
print("Draft 4 types:", list(draft4_type_checker._type_checkers.keys()))
# Excludes 'any'
# Draft 6+ changes integer handling
print(draft4_type_checker.is_type(42.0, "integer")) # False
print(draft6_type_checker.is_type(42.0, "integer")) # True (if 42.0.is_integer())from jsonschema import Draft202012Validator
from jsonschema._types import TypeChecker
# Create validator with custom type checking
def is_non_empty_string(checker, instance):
"""Check if instance is a non-empty string."""
return isinstance(instance, str) and len(instance) > 0
custom_checker = TypeChecker().redefine_many({
"string": is_string,
"non-empty-string": is_non_empty_string,
"array": is_array,
"object": is_object,
"number": is_number,
"integer": is_integer,
"boolean": is_bool,
"null": is_null
})
# Import required items
from jsonschema._types import is_string, is_array, is_object, is_number, is_integer, is_bool, is_null
# Use in validator
validator = Draft202012Validator(
{"type": "non-empty-string"},
type_checker=custom_checker
)
validator.validate("hello") # Valid
try:
validator.validate("") # Invalid - empty string
except ValidationError as e:
print(f"Type error: {e.message}")All type checking functions must follow this signature:
def my_type_checker(checker, instance):
"""
Custom type checking function.
Parameters:
- checker: The TypeChecker instance calling this function
- instance: The value to type-check
Returns:
- bool: True if instance is of this type, False otherwise
"""
# Custom type checking logic here
return isinstance(instance, MyCustomType)Install with Tessl CLI
npx tessl i tessl/pypi-jsonschema