--- title: "FastAPI ORM @property for JSON-Column Fields — No Migration Needed" aliases: [orm-property-json-column, sqlalchemy-json-property, fastapi-no-migration-field] tags: [fastapi, sqlalchemy, python, orm, json, migration, pattern] sources: - "daily/2026-05-07.md" created: 2026-05-07 updated: 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 ```python 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 ```python 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 - [[wiki/concepts/fastapi-response-model-silent-field-strip]] — FastAPI schema omission silently strips fields; adding a `@property` only helps if the field is also in the Pydantic model - [[wiki/tech-patterns/fastapi-python-docker]] — FastAPI deployment context ## 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