Storing Pydantic Models in JSONB Columns¶
You can store Pydantic models (and lists or dicts of them) in JSON or JSONB database columns using the PydanticJSONB
utility.
This is especially useful when:
- You want to persist flexible, nested data structures in your models.
- You prefer to avoid separate relational tables for structured fields like metadata, config, or address.
- You want automatic serialization and deserialization using Pydantic.
Usage¶
You can use it with SQLModel like this:
from typing import Optional
from pydantic import BaseModel
from sqlmodel import SQLModel, Field, Column
from sqlmodel.sql.sqltypes import PydanticJSONB
class Address(BaseModel):
street: str
city: str
class User(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
address: Address = Field(sa_column=Column(PydanticJSONB(Address)))
This will store the address
field as a JSONB
column in PostgreSQL and automatically serialize/deserialize to and from the Address
Pydantic model.
If you're using a list or dict of models, PydanticJSONB
supports that too:
Field(sa_column=Column(PydanticJSONB(List[SomeModel])))
Field(sa_column=Column(PydanticJSONB(Dict[str, SomeModel])))
Requirements¶
- PostgreSQL (for full
JSONB
support). - Pydantic v2.
- SQLAlchemy 2.x.
Limitations¶
Nested Model Updates¶
Currently, updating attributes inside a nested Pydantic model doesn't automatically trigger a database update. This is similar to how plain dictionaries work in SQLAlchemy. For example:
# This won't trigger a database update
row = select(...) # some MyTable row
row.data.x = 1
db.add(row) # no effect, change isn't detected
To update nested model attributes, you need to reassign the entire model:
# Workaround: Create a new instance and reassign
updated = ExtraData(**row.data.model_dump())
updated.x = 1
row.data = updated
db.add(row)
This limitation will be addressed in a future update using MutableDict
to enable change tracking for nested fields. The MutableDict
implementation will emit change events when the contents of the dictionary are altered, including when values are added or removed.
Notes¶
- Falls back to
JSON
ifJSONB
is not available. - Only tested with PostgreSQL at the moment.