Marshmallow 实现序列化和反序列化


2. Marshmallow

Marshmallow 是一个强大的轮子,很好的实现了 object -> dict , objects -> list , string -> dict 和 string -> list 。

Marshmallow is an ORM/ODM/framework-agnostic library for converting complex datatypes, such as objects, to and from native Python datatypes.

-- From marshmallow官方文档

Marshmallow的使用,将从下面几个方面展开,在开始之前,首先需要一个用于序列化和反序列化的类,我直接与marshmallow官方文档保持一致了:

class User(object): def __init__(self, name, email): self.name = name self.email = email self.created_at = dt.datetime.now() Schema

要对一个类(记为 Class_A ,以便表达)进行序列化和反序列化,首先要创建一个与之对应的类(记 Class_A' ),负责实现 Class_A 的序列化、序列化和数据校验等, Class_A' 就是schema,即:

Schema是序列化功能的载体,每个需要被序列化或反序列化的类,都要设计一个相应的Schema,以实现相关功能。Schema中的字段,是被序列化的类的映射,如:

class UserSchema(Schema): name = fields.Str() email = fields.Email() created_at = fields.DateTime()

创建 schema 时,schema中的字段必须与类的成员命名一致,不一致的字段无法被序列化和反序列化。

序列化

序列化使用schema中的 dump() 或 dumps() 方法,其中, dump() 方法实现 obj -> dict , dumps() 方法实现 obj -> string ,由于Flask能直接序列化dict,所以通常Flask与Marshmallow配合序列化时,用 dump() 方法即可。

from marshmallow import pprintuser = User(name="Monty", email="[email protected]")schema = UserSchema()result = schema.dump(user)pprint(result.data)json_result = schema.dumps(user)pprint(json_result.data) 反序列化

反序列化基于schema中的 load() 或 loads() 方法,默认情况下, load() 方法将一个传入的 dict ,结合schema的约定,再转换为一个 dict ,而 loads() 方法的传入参数是 json格式的string ,同样将传入数据转换为符合规范的 dict 。由于调用 load() 或 loads() 方法时,会执行下面提到的 数据校验 ,所以在开发RESTful API时,对传入数据执行 load() 或 loads() 方法是必要的。 load() 方法使用如下:

from pprint import pprintuser_data = { 'created_at': '2014-08-11T05:26:03.869245', 'email': u'[email protected]', 'name': u'Ken'}schema = UserSchema()result = schema.load(user_data)pprint(result.data)

对反序列化而言,将传入的 dict 变成 object 更加有意义。在Marshmallow中, dict -> object 的方法需要自己实现,然后在该方法前面加上一个decoration: post_load 即可,即:

from marshmallow import Schema, fields, post_loadclass UserSchema(Schema): name = fields.Str() email = fields.Email() created_at = fields.DateTime() @post_load def make_user(self, data): return User(**data)

这样每次调用 load() 方法时,会按照 make_user 的逻辑,返回一个 User类 对象。

user_data = { 'name': 'Ronnie', 'email': '[email protected]'}schema = UserSchema()result = schema.load(user_data)result.data # => <User(name='Ronnie')> Objects <-> List

上面的序列化和反序列化,是针对一个object而言的,对于objects的处理,只需在schema中增加一个参数: many=True ,即:

user1 = User(name="Mick", email="[email protected]")user2 = User(name="Keith", email="[email protected]")users = [user1, user2]# option 1:schema = UserSchema(many=True)result = schema.dump(users)# Option 2:schema = UserSchema()result = schema.dump(users, many=True)result.data Validation

Marshmallow中的Validation功能用于校验客户端传入的数据是否规范,通常用于创建和修改数据。

Validation可分为 field level validation 和 schema level validation ,创建schema时,实现必要Validation是必须的,由于详细阐述占用的篇幅会比较长,这部分内容请大家直接查看官方文档:

Field level validation Schema level validation Partial Loading

按照RESTful架构风格的要求,更新数据使用HTTP方法中的 PUT 或 PATCH 方法,使用 PUT 方法时,需要把完整的数据全部传给服务器,使用 PATCH 方法时,只需把需要改动的部分数据传给服务器即可。因此,当使用 PATCH 方法时,传入数据存在无法通过Marshmallow 数据校验的风险,为了避免这种情况,需要借助Partial Loading功能。

实现Partial Loadig只要在schema中增加一个 partial 参数即可:

class UserSchema(Schema): name = fields.String(required=True) age = fields.Integer(required=True)data, errors = UserSchema().load({'age': 42}, partial=True)# OR UserSchema(partial=True).load({'age': 42})data, errors # => ({'age': 42}, {}) Context

关于Marshmallow,最后想提的一点是, context :

The context attribute of a Schema is a general-purpose store for extra information that may be needed for (de)serialization. It may be used in both Schema and Field methods.

Context 是很用的,比如,更新数据时,更新的是一个数据库中已有的对象,反序列化操作如果能基于该对象来反序列化就再好不过了,实现这个小需求一种可行方案是,把该对象放到context中,schema中针对该对象实现相关业务逻辑。

3. Marshmallow 与 Flask 的结合

Marshmallow有一个flask extention: flask-marshmallow ,该扩展与 Flask-SQLAlchemy 和 marshmallow-sqlalchemy 有集成,有兴趣的同学不妨研究一下。

对Gevin而言,数据库抽象层通常使用 Flask-MongoEngine ,marshmallow本身就够用了。

Talk is cheap, show me the code.

关于Marshmallow在Flask中的应用,大家可以查看我在GitHub上的这两个代码文件:

JuneMessage/schemas.py JuneMessage/api_views.py

以上两个代码文件来自我尚在开发中的项目 JuneMessage ,RESTful API和schema部分已经完成,可以参考一下。另外,schema部分不仅把上面提到的要点均实现了一遍,在代码的组织上也更进一步,看代码比单纯看上面的文字应该更有意义。

注:

Flask 功能基础部分,附加一个说明。

需要注意的是,处于安全考虑,Flask不允许直接返回 list ,即:

data = [{'a':1, 'b':2}, {'c':3, 'd':4}]# This is [email protected]('/list1/')def test_list(): return jsonify(result=data)# This fails @app.route('/list1/')def test_list(): return jsonify(data)

由于这段内容不是本文主题,所以放到最后做补充说明。

发布了18 篇原创文章 · 获赞 10 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_34146899/article/details/52650971