目次

目次

SQLAlchemy x marshmallowでModelからJSONへの変換を楽に行う

松木佑徒
松木佑徒
最終更新日2017/09/13 投稿日2017/09/13

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変換用のクラスを作成して詰め替えていたのですが、 単純な詰め替え作業は少ないコードで書けて拡張性もあるので便利かと思います。

松木佑徒

目次