JSON Property¶
GINO provides additional support to leverage native JSON type in the database as flexible GINO model fields.
Quick Start¶
from gino import Gino
from sqlalchemy.dialects.postgresql import JSONB
db = Gino()
class User(db.Model):
    __tablename__ = "users"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    profile = db.Column(JSONB, nullable=False, server_default="{}")
    age = db.IntegerProperty()
    birthday = db.DateTimeProperty()
The age and birthday are JSON properties stored in the profile column. You
may use them the same way as a normal GINO model field:
u = await User.create(name="daisy", age=18)
print(u.name, u.age)  # daisy 18
Note
profile is the default column name for all JSON properties in a model. If you
need a different column name for some JSON properties, you’ll need to specify
explicitly:
audit_profile = db.Column(JSON, nullable=False, server_default="{}")
access_log = db.ArrayProperty(prop_name="audit_profile")
abnormal_detected = db.BooleanProperty(prop_name="audit_profile")
Using JSON properties in queries is supported:
await User.query.where(User.age > 16).gino.all()
This is simply translated into a native JSON query like this:
SELECT users.id, users.name, users.profile
FROM users
WHERE CAST((users.profile ->> $1) AS INTEGER) > $2;  -- ('age', 16)
Datetime type is very much the same:
from datetime import datetime
await User.query.where(User.birthday > datetime(1990, 1, 1)).gino.all()
And the generated SQL:
SELECT users.id, users.name, users.profile
FROM users
WHERE CAST((users.profile ->> $1) AS TIMESTAMP WITHOUT TIME ZONE) > $2
-- ('birthday', datetime.datetime(1990, 1, 1, 0, 0))
Here’s a list of all the supported JSON properties:
| JSON Property | Python type | JSON type | Database Type | 
|---|---|---|---|
| 
 | 
 | ||
| 
 | 
 | ||
| 
 | 
 | ||
| 
 | 
 | ||
| 
 | JSON | ||
| 
 | JSON | 
Hooks¶
JSON property provides 2 instance-level hooks to customize the data:
class User(db.Model):
    __tablename__ = "users"
    id = db.Column(db.Integer, primary_key=True)
    profile = db.Column(JSONB, nullable=False, server_default="{}")
    age = db.IntegerProperty()
    @age.before_set
    def age(self, val):
        return val - 1
    @age.after_get
    def age(self, val):
        return val + 1
u = await User.create(name="daisy", age=18)
print(u.name, u.profile, u.age)  # daisy {'age': 17} 18
And 1 class-level hook to customize the SQLAlchemy expression of the property:
class User(db.Model):
    __tablename__ = "users"
    id = db.Column(db.Integer, primary_key=True)
    profile = db.Column(JSONB, nullable=False, server_default="{}")
    height = db.JSONProperty()
    @height.expression
    def height(cls, exp):
        return exp.cast(db.Float)  # CAST(profile -> 'height' AS FLOAT)
Create Index on JSON Properties¶
We’ll need to use declared_attr() to wait until the model class
is initialized. The rest is very much the same as defining a usual index:
class User(db.Model):
    __tablename__ = "users"
    id = db.Column(db.Integer, primary_key=True)
    profile = db.Column(JSONB, nullable=False, server_default="{}")
    age = db.IntegerProperty()
    @db.declared_attr
    def age_idx(cls):
        return db.Index("age_idx", cls.age)
This will lead to the SQL below executed if you run db.gino.create_all():
CREATE INDEX age_idx ON users (CAST(profile ->> 'age' AS INTEGER));
Warning
Alembic doesn’t support auto-generating revisions for functional indexes yet. You’ll need to manually edit the revision. Please follow this issue for updates.
