Flask-SQLAlchemyを使用したAPIを作成していてDBから取得したModelをJSONに変換しようとすると、 SQLAlchemyのModelはそのままJSONに変換できないので何かに詰め替えを行う必要があります。
results = User.query.all()
# JSONに変換できずエラーになる
return jsonify({'status': 'ok', 'users': results})
pythonのmarshmallowというライブラリは、 pythonオブジェクトをJSONに変換したりその逆を行ったりするためのスキーマを定義するためのライブラリです。
今回は、marshmallowを使用してSQLAlchemyのModelをJSONに変換するためのスキーマを定義します。
実装例
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from flask_marshmallow.fields import fields
db = SQLAlchemy()
class User(db.Model):
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
first_name = db.Column(db.String(100), default=None)
last_name = db.Column(db.String(100), default=None)
created_at = db.Column(db.DateTime, default=datetime.now)
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
ma = Marshmallow()
class UserSchema(ma.ModelSchema):
class Meta:
model = User
created_at = fields.DateTime('%Y-%m-%dT%H:%M:%S+09:00')
updated_at = fields.DateTime('%Y-%m-%dT%H:%M:%S+09:00')
full_name = fields.Method("join_full_name")
def join_full_name(self, obj):
return '{first_name} {last_name}'.format(last_name=obj.last_name, first_name=obj.first_name)
使用例
results = User.query.all()
return jsonify({'status': 'ok', 'users': UserSchema(many=True, exclude=('first_name', 'last_name')).dump(results).data})
出力結果
{
"status": "ok",
"users": [
{
"id": 1,
"full_name": "yuto matsuki",
"created_at": "2017-08-19T19:50:39+09:00",
"updated_at": "2017-08-19T19:50:39+09:00"
},
{
...
}
...
]
}
解説
SQLAlchemyでuserテーブルのModelを定義します。 5つのカラム(id,first_name,last_name,created_at,updated_at)を作成します。
db = SQLAlchemy()
class User(db.Model):
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
first_name = db.Column(db.String(100), default=None)
last_name = db.Column(db.String(100), default=None)
created_at = db.Column(db.DateTime, default=datetime.now)
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
marshmallowでModelSchemaを定義します。 SQLAlchemyのModelの場合は以下のようにclass Metaで指定するだけで完了です。
ma = Marshmallow()
class UserSchema(ma.ModelSchema):
class Meta:
model = User
ここまでを使ってSQLAlchemyで取得したオブジェクトを変換すると
UserSchema().dump(<Userオブジェクト>).data
以下のようなJSONが得られます。
{
"id": 1,
"first_name": "yuto",
"last_name": "matsuki",
"created_at": "2017-08-19T19:50:39+00:00",
"updated_at": "2017-08-19T19:50:39+00:00"
}
ここで問題になるのが、MarshmallowのDateTime型が自動でUTCに変換されてしまう点です。
"2017-08-19T19:50:39+00:00"
そのため、日付フォーマットを指定してあげる必要があります。 以下のように書くことでプロパティを上書きできるようです。
class UserSchema(ma.ModelSchema):
class Meta:
model = User
created_at = fields.DateTime('%Y-%m-%dT%H:%M:%S+09:00')
updated_at = fields.DateTime('%Y-%m-%dT%H:%M:%S+09:00')
また、Modelにないプロパティを追加したい場合もあります。 例えばカスタムフィールドのMethod Fieldsを使用すると 以下のように元のModelを加工したプロパティを追加することができます。
class UserSchema(ma.ModelSchema):
class Meta:
model = User
full_name = fields.Method("join_full_name")
def join_full_name(self, obj):
return '{first_name} {last_name}'.format(last_name=obj.last_name, first_name=obj.first_name)
また、Schemaのインスタンス生成時に
excludeを使用することで不要なプロパティを削除できます。
UserSchema(exclude=('first_name', 'last_name')).dump(<Userオブジェクト>).data
以下のようなJSONが得られます。
{
"id": 1,
"full_name": "yuto matsuki",
"created_at": "2017-08-19T19:50:39+00:00",
"updated_at": "2017-08-19T19:50:39+00:00"
}
その他、スキーマの定義を行えばリレーションのあるテーブルを一緒に取って来ることもできたりします。 Marshmallowを知るまではSQLAlchemyのModelからJSON変換用のクラスを作成して詰め替えていたのですが、 単純な詰め替え作業は少ないコードで書けて拡張性もあるので便利かと思います。
松木佑徒