Videos
This has been discussed some time ago and Samuel Colvin said he didn't want to pursue this as a feature for Pydantic.
If you are fine with code generation instead of actual runtime creation of models, you can use the datamodel-code-generator.
To be honest, I struggle to see the use case for generating complex models at runtime, seeing as their main purpose is validation, implying that you think about correct schema before running your program. But that is just my view.
For simple models I guess you can throw together your own logic for this fairly quickly.
If do you need something more sophisticated, the aforementioned library does offer some extensibility. You should be able to import and inherit from some of their classes like the JsonSchemaParser. Maybe that will get you somewhere.
Ultimately I think this becomes non-trivial very quickly, which is why Pydantic's maintainer didn't want to deal with it and why there is a whole separate project for this.
Updated @Alon's answer to handle nested modals:
from typing import Any, Type, Optional
from enum import Enum
from pydantic import BaseModel, Field, create_model
def json_schema_to_base_model(schema: dict[str, Any]) -> Type[BaseModel]:
type_mapping: dict[str, type] = {
"string": str,
"integer": int,
"number": float,
"boolean": bool,
"array": list,
"object": dict,
}
properties = schema.get("properties", {})
required_fields = schema.get("required", [])
model_fields = {}
def process_field(field_name: str, field_props: dict[str, Any]) -> tuple:
"""Recursively processes a field and returns its type and Field instance."""
json_type = field_props.get("type", "string")
enum_values = field_props.get("enum")
# Handle Enums
if enum_values:
enum_name: str = f"{field_name.capitalize()}Enum"
field_type = Enum(enum_name, {v: v for v in enum_values})
# Handle Nested Objects
elif json_type == "object" and "properties" in field_props:
field_type = json_schema_to_base_model(
field_props
) # Recursively create submodel
# Handle Arrays with Nested Objects
elif json_type == "array" and "items" in field_props:
item_props = field_props["items"]
if item_props.get("type") == "object":
item_type: type[BaseModel] = json_schema_to_base_model(item_props)
else:
item_type: type = type_mapping.get(item_props.get("type"), Any)
field_type = list[item_type]
else:
field_type = type_mapping.get(json_type, Any)
# Handle default values and optionality
default_value = field_props.get("default", ...)
nullable = field_props.get("nullable", False)
description = field_props.get("title", "")
if nullable:
field_type = Optional[field_type]
if field_name not in required_fields:
default_value = field_props.get("default", None)
return field_type, Field(default_value, description=description)
# Process each field
for field_name, field_props in properties.items():
model_fields[field_name] = process_field(field_name, field_props)
return create_model(schema.get("title", "DynamicModel"), **model_fields)
Example Schema
schema = {
"title": "User",
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
"is_active": {"type": "boolean"},
"address": {
"type": "object",
"properties": {
"street": {"type": "string"},
"city": {"type": "string"},
"zipcode": {"type": "integer"},
},
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["admin", "user", "guest"]
}
}
},
"required": ["name", "age"]
}
Generate the Pydantic model
DynamicModel = json_schema_to_base_model(schema)
Example usage
print(DynamicModel.schema_json(indent=2))