Convert JSON Schema definitions to Pydantic models dynamically at runtime
Project description
jsonschema-pydantic-converter
Convert JSON Schema definitions to Pydantic models dynamically at runtime.
Overview
jsonschema-pydantic-converter is a Python library that transforms JSON Schema dictionaries into Pydantic v2 models. This is useful when you need to work with dynamic schemas, validate data against JSON Schema specifications, or bridge JSON Schema-based systems with Pydantic-based applications.
Features
- Dynamic Model Generation: Convert JSON Schema to Pydantic models at runtime
- TypeAdapter Support: Generate TypeAdapters for enhanced validation and serialization
- Comprehensive Type Support:
- Primitive types (string, number, integer, boolean, null)
- Arrays with typed items and tuples (prefixItems)
- Nested objects
- Enums (with and without explicit type)
- Union types (anyOf, oneOf)
- Combined schemas (allOf)
- Negation (not)
- Constant values (const)
- Boolean schemas (true/false)
- Validation Constraints: Full support for Pydantic-native constraints
- String: minLength, maxLength, pattern
- Numeric: minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf
- Array: minItems, maxItems
- Schema References: Support for
$refand$defs/definitions - Field Metadata: Preserves titles, descriptions, and default values
- Self-References: Handle recursive schema definitions
- Reserved Property Names: Automatically handles properties that would conflict with Pydantic (e.g.,
_hidden,schema,copy,validate,model_fields) by renaming them internally and using aliases to preserve original JSON names - Pydantic v2 Compatible: Built for Pydantic 2.0+
Installation
pip install jsonschema-pydantic-converter
Or using uv:
uv add jsonschema-pydantic-converter
Usage
Basic Example (using transform)
from jsonschema_pydantic_converter import transform
# Define a JSON Schema
schema = {
"type": "object",
"properties": {
"name": {"type": "string", "description": "User's name"},
"age": {"type": "integer", "description": "User's age"},
"email": {"type": "string"}
},
"required": ["name", "age"]
}
# Convert to Pydantic model
UserModel = transform(schema)
# Use the model
user = UserModel(name="John Doe", age=30, email="john@example.com")
print(user.model_dump())
# {'name': 'John Doe', 'age': 30, 'email': 'john@example.com'}
Using TypeAdapter for Validation
The create_type_adapter function returns a Pydantic TypeAdapter, which provides additional validation and serialization capabilities:
from jsonschema_pydantic_converter import create_type_adapter
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
"email": {"type": "string"}
},
"required": ["name", "age"]
}
# Create TypeAdapter
adapter = create_type_adapter(schema)
# Validate Python objects
user = adapter.validate_python({"name": "John Doe", "age": 30, "email": "john@example.com"})
print(user)
# Validate JSON strings directly
json_str = '{"name": "Jane Doe", "age": 25}'
user = adapter.validate_json(json_str)
# Serialize back to Python dict
user_dict = adapter.dump_python(user)
print(user_dict)
# {'name': 'Jane Doe', 'age': 25, 'email': None}
When to use transform vs create_type_adapter:
create_type_adapter()is recommended for all new code - it handles any JSON schema type and provides validation/serialization methods- Use
transform()when you need compatibility with codebases expectingBaseModel. For non-object schemas, it will return aRootModel.
Working with Enums
from jsonschema_pydantic_converter import create_type_adapter
schema = {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": ["active", "inactive", "pending"]
}
}
}
adapter = create_type_adapter(schema)
obj = adapter.validate_python({"status": "active"})
Nested Objects
from jsonschema_pydantic_converter import create_type_adapter
schema = {
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"}
},
"required": ["name"]
}
}
}
adapter = create_type_adapter(schema)
data = adapter.validate_python({"user": {"name": "Alice", "email": "alice@example.com"}})
Arrays
from jsonschema_pydantic_converter import create_type_adapter
schema = {
"type": "object",
"properties": {
"tags": {
"type": "array",
"items": {"type": "string"}
}
}
}
adapter = create_type_adapter(schema)
obj = adapter.validate_python({"tags": ["python", "pydantic", "json-schema"]})
Schema with References
from jsonschema_pydantic_converter import create_type_adapter
schema = {
"type": "object",
"properties": {
"person": {"$ref": "#/$defs/Person"}
},
"$defs": {
"Person": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"}
}
}
}
}
adapter = create_type_adapter(schema)
person = adapter.validate_python({"person": {"name": "Bob", "age": 25}})
Union Types (anyOf)
from jsonschema_pydantic_converter import create_type_adapter
schema = {
"type": "object",
"properties": {
"value": {
"anyOf": [
{"type": "string"},
{"type": "integer"}
]
}
}
}
adapter = create_type_adapter(schema)
obj1 = adapter.validate_python({"value": "text"})
obj2 = adapter.validate_python({"value": 42})
Validation Constraints
from jsonschema_pydantic_converter import create_type_adapter
schema = {
"type": "object",
"properties": {
"username": {
"type": "string",
"minLength": 3,
"maxLength": 20,
"pattern": "^[a-z0-9_]+$"
},
"age": {
"type": "integer",
"minimum": 0,
"maximum": 150
},
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"multipleOf": 0.5
}
}
}
adapter = create_type_adapter(schema)
# Valid data
obj = adapter.validate_python({
"username": "john_doe",
"age": 25,
"score": 85.5
})
# Invalid - will raise ValidationError
# adapter.validate_python({"username": "ab"}) # Too short
# adapter.validate_python({"age": -1}) # Below minimum
Constant Values (const)
from jsonschema_pydantic_converter import create_type_adapter
schema = {
"type": "object",
"properties": {
"country": {"const": "United States"},
"version": {"const": 1}
}
}
adapter = create_type_adapter(schema)
# Valid - exact match
obj = adapter.validate_python({"country": "United States", "version": 1})
# Invalid - will raise ValidationError
# adapter.validate_python({"country": "Canada", "version": 1})
Negation (not)
from jsonschema_pydantic_converter import create_type_adapter
schema = {
"type": "object",
"properties": {
"value": {"not": {"type": "string"}}
}
}
adapter = create_type_adapter(schema)
# Valid - not a string
obj1 = adapter.validate_python({"value": 42})
obj2 = adapter.validate_python({"value": [1, 2, 3]})
# Invalid - is a string
# adapter.validate_python({"value": "text"})
Combined Schemas (allOf)
from jsonschema_pydantic_converter import create_type_adapter
schema = {
"allOf": [
{
"type": "object",
"properties": {"name": {"type": "string"}},
"required": ["name"]
},
{
"type": "object",
"properties": {"age": {"type": "integer"}},
"required": ["age"]
}
]
}
adapter = create_type_adapter(schema)
# Valid - satisfies all schemas
obj = adapter.validate_python({"name": "Alice", "age": 30})
# Invalid - missing required fields
# adapter.validate_python({"name": "Alice"})
Tuples (prefixItems)
from jsonschema_pydantic_converter import create_type_adapter
schema = {
"type": "array",
"prefixItems": [
{"type": "string"},
{"type": "integer"},
{"type": "boolean"}
]
}
adapter = create_type_adapter(schema)
# Valid tuple
result = adapter.validate_python(["hello", 42, True])
# Returns: ("hello", 42, True)
Boolean Schemas
from jsonschema_pydantic_converter import create_type_adapter
# Schema that accepts anything
schema_true = True
adapter_true = create_type_adapter(schema_true)
adapter_true.validate_python("anything") # Valid
adapter_true.validate_python(42) # Valid
adapter_true.validate_python([1, 2, 3]) # Valid
# Schema that rejects everything
schema_false = False
adapter_false = create_type_adapter(schema_false)
# adapter_false.validate_python("anything") # Invalid - raises ValidationError
Reserved Property Names
Properties that start with underscores or shadow Pydantic BaseModel attributes (like schema, copy, validate) are automatically renamed to safe Python identifiers, with aliases preserving the original JSON names:
from jsonschema_pydantic_converter import transform
schema = {
"type": "object",
"properties": {
"_file": {"type": "string"},
"schema": {"type": "string"},
"name": {"type": "string"},
},
"required": ["_file", "schema", "name"],
}
Model = transform(schema)
# Validate using original JSON property names
instance = Model.model_validate({"_file": "a.txt", "schema": "v1", "name": "test"})
# model_dump() outputs original names by default
print(instance.model_dump())
# {'_file': 'a.txt', 'schema': 'v1', 'name': 'test'}
# model_json_schema() also uses original names
print(Model.model_json_schema()["properties"].keys())
# dict_keys(['_file', 'schema', 'name'])
# Internal Python field names are accessible too
print(instance.file) # 'a.txt' (leading _ stripped)
print(instance.schema_) # 'v1' (trailing _ added to avoid collision)
print(instance.name) # 'test' (unchanged)
Note (Pydantic <2.11): The
serialize_by_aliasconfig option was introduced in Pydantic 2.11. On older versions,model_dump()returns internal field names by default. Useinstance.model_dump(by_alias=True)explicitly to get the original JSON property names. A runtime warning is emitted at import time when running on Pydantic <2.11.
Development Setup
Prerequisites
- Python 3.10 or higher
- uv (recommended) or pip
Clone the Repository
git clone https://github.com/akshaylive/jsonschema-pydantic-converter.git
cd jsonschema-pydantic-converter
Install Dependencies
Using uv (recommended):
uv sync
Using pip:
pip install -e .
pip install mypy ruff pytest pytest-cov
Run Tests
# Using uv
uv run pytest
# With coverage
uv run pytest --cov=src --cov-report=html
# Using pytest directly (if in activated venv)
pytest
Code Quality
The project uses several tools to maintain code quality:
# Type checking with mypy
uv run mypy src/
# Linting with ruff
uv run ruff check .
# Format code with ruff
uv run ruff format .
Contributing
Contributions are welcome! Here's how you can help:
Reporting Issues
- Check existing issues before creating a new one
- Provide a clear description of the problem
- Include a minimal reproducible example
- Specify your Python and Pydantic versions
Submitting Pull Requests
- Fork the repository
- Create a new branch for your feature/fix:
git checkout -b feature/your-feature-name
- Make your changes and ensure:
- All tests pass:
uv run pytest - Code is properly formatted:
uv run ruff format . - No linting errors:
uv run ruff check . - Type checking passes:
uv run mypy src/
- All tests pass:
- Add tests for new functionality
- Update documentation if needed
- Commit your changes with clear commit messages
- Push to your fork and submit a pull request
Code Style
- Follow PEP 8 guidelines
- Use Google-style docstrings
- Type hints are required for all functions
- Line length: 88 characters (Black/Ruff default)
Development Guidelines
- Write tests for all new features
- Maintain backwards compatibility when possible
- Update the README for user-facing changes
- Keep dependencies minimal
Limitations
- Optional fields without defaults are set to
Nonerather than usingOptional[T]type annotation to maintain JSON Schema round-trip consistency - When
allOfcontains$refreferences, the generatedjson_schema()output may not preserve the exact original structure (validation still works correctly) - Some advanced JSON Schema features are not yet supported:
$anchorreferences (causes syntax errors with forward references)$dynamicRef/$dynamicAnchor(draft 2020-12 advanced features)- Full enforcement of:
uniqueItems,contains,propertyNames,patternProperties,formatvalidation if-then-elseconditionals (base type is used, but conditionals are not enforced)
- Complex schema combinations may require testing for edge cases
License
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Maintainer
Akshaya Shanbhogue - akshay.live@gmail.com
Links
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jsonschema_pydantic_converter-0.3.1.tar.gz.
File metadata
- Download URL: jsonschema_pydantic_converter-0.3.1.tar.gz
- Upload date:
- Size: 68.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7755f6cb6e87bb07cfb29d5689142a752a718346a7a4ca0f2499524f7bc2b60e
|
|
| MD5 |
53749b6d22048b1b709b4c4e80eff04a
|
|
| BLAKE2b-256 |
1e13ac52857f7c17956ad565c5dab1aca561cad654c92951aafbde7ed8b812f9
|
Provenance
The following attestation bundles were made for jsonschema_pydantic_converter-0.3.1.tar.gz:
Publisher:
publish.yml on akshaylive/jsonschema-pydantic-converter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jsonschema_pydantic_converter-0.3.1.tar.gz -
Subject digest:
7755f6cb6e87bb07cfb29d5689142a752a718346a7a4ca0f2499524f7bc2b60e - Sigstore transparency entry: 1163278052
- Sigstore integration time:
-
Permalink:
akshaylive/jsonschema-pydantic-converter@16a7bd1e69f3f94ff1a69d4b4856d3bd2327bf4c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/akshaylive
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@16a7bd1e69f3f94ff1a69d4b4856d3bd2327bf4c -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file jsonschema_pydantic_converter-0.3.1-py3-none-any.whl.
File metadata
- Download URL: jsonschema_pydantic_converter-0.3.1-py3-none-any.whl
- Upload date:
- Size: 20.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
03be8a8a0ac9a230c669f998bf27ce9908584fbacc475a0019af9b6f915cb53e
|
|
| MD5 |
80c7e8e0ff10da685364e82fc124c283
|
|
| BLAKE2b-256 |
13ca13df0a945b3845985df740117dff021ba4c0841e46933ef850d3e0d42dd7
|
Provenance
The following attestation bundles were made for jsonschema_pydantic_converter-0.3.1-py3-none-any.whl:
Publisher:
publish.yml on akshaylive/jsonschema-pydantic-converter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jsonschema_pydantic_converter-0.3.1-py3-none-any.whl -
Subject digest:
03be8a8a0ac9a230c669f998bf27ce9908584fbacc475a0019af9b6f915cb53e - Sigstore transparency entry: 1163278165
- Sigstore integration time:
-
Permalink:
akshaylive/jsonschema-pydantic-converter@16a7bd1e69f3f94ff1a69d4b4856d3bd2327bf4c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/akshaylive
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@16a7bd1e69f3f94ff1a69d4b4856d3bd2327bf4c -
Trigger Event:
workflow_dispatch
-
Statement type: