| title |
aliases |
tags |
sources |
created |
updated |
| FastAPI ORM @property for JSON-Column Fields — No Migration Needed |
| orm-property-json-column |
| sqlalchemy-json-property |
| fastapi-no-migration-field |
|
| fastapi |
| sqlalchemy |
| python |
| orm |
| json |
| migration |
| pattern |
|
|
2026-05-07 |
2026-05-07 |
FastAPI ORM @property for JSON-Column Fields — No Migration Needed
When an ORM model already stores data in a JSON column (e.g. fields_json), new read-only display fields can be exposed via Python @property on the model class — zero Alembic migration, zero schema change, forward-compatible.
Key Points
- If the data already exists inside a JSON column, a
@property surfaces it as a first-class attribute on the model
- No
ALTER TABLE, no Alembic revision, no downtime — the column is already there
- Works seamlessly with FastAPI's
response_model: add the field to the Pydantic schema and it resolves via the property
- Use this pattern for read-only derived or display fields; for fields that need filtering/ordering in SQL, a real column is still required
- The property must be declared with a
@property decorator; SQLAlchemy does not auto-expose JSON sub-keys
Pattern
from sqlalchemy import Column, String, JSON
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass
class Task(Base):
__tablename__ = "tasks"
id = Column(String, primary_key=True)
title = Column(String)
fields_json = Column(JSON, default=dict) # existing JSON column
# ← New display fields via @property — no migration needed
@property
def tags(self) -> list[str]:
return (self.fields_json or {}).get("tags", [])
@property
def priority(self) -> str | None:
return (self.fields_json or {}).get("priority")
Pydantic Schema
from pydantic import BaseModel, ConfigDict
class TaskResponse(BaseModel):
model_config = ConfigDict(from_attributes=True) # enables ORM mode
id: str
title: str
tags: list[str] = [] # ← resolved from ORM @property
priority: str | None = None
[!info] from_attributes=True is required
Pydantic v2 from_attributes=True (previously orm_mode = True in v1) allows the schema to read Python object attributes, including @property, not just dict keys.
When to Use
| Scenario |
Use @property |
Use real column |
| Display-only field, data already in JSON |
✓ |
— |
Field needs SQL WHERE/ORDER BY |
— |
✓ |
| No Alembic migration budget |
✓ |
— |
| Aggregation or JOIN on this field |
— |
✓ |
Related Concepts
Sources
- daily/2026-05-07.md — Session 12:09: Vue 3 + FastAPI + Planka + Azure DevOps integration;
tags and ado_work_item_id surfaced via @property from fields_json without adding DB columns