Flask RESTful API开发之序列化与反序列化

序列化(序列化)与反序列化(Deserialization)是RESTful API开发中绕不开的一环,开发时,序列化与反序列化的功能实现中通常也包含数据校验(验证)相关的业务本文结合我的实践经验,介绍一种Flask RESTful API开发中实现序列化和反序列化的方法,如果想了解更多相关的理论要点,可以参考

Flask相关功能基础

python内置数据类型中的dict和list,可以直接序列化为文本,如:

Dict和list能够直接被序列化为文本,如下:

def test_list(): 
    data = [{'a':1, 'b':2}, {'c':3, 'd':4}] 
    return jsonify(result=data)

def test_dict1(): 
    data = {'a':1, 'b':2, 'c':3} 
    return jsonify(data)

def test_dict2(): 
   data = {'a':1, 'b':2, 'c':3} 
   return jsonify(**data)

def test_dict3():
    data = {'a':1, 'b':2, 'c':3} 
    return jsonify(result=data)

其中,jsonify的作用是,把dict或list转换为string(类似于json.dumps()),同时,在响应的头中,添加

content-type =application/json,由于RESTful API通常以json格式作为载体,该方法算是一个捷径。由此,
对象的序列化,实现只要object -> dict或objects -> list即可。

反序列化,通常分两步实现:

1、将请求中的文本数据转换为蟒原生的字典或列表
2、基于字典为初始化数据,创建类的实例

反序列化实现的关键是上面第一步,而这一步实现非常容易,如果数据载体为json,调用只需

request.get_json()方法,即可将传入的文本数据转换为字典或列表。

Marshmallow

Marshmallow是一个强大的轮子,很好的实现了

object -> dict,objects -> list,string -> dict和string -> list。

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 pprint

user = User(name="Monty", email="monty@python.org")
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 pprint

user_data = {
    'created_at': '2014-08-11T05:26:03.869245',
    'email': u'ken@yahoo.com',
    '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_load

class 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': 'ronnie@stones.com'
}
schema = UserSchema()
result = schema.load(user_data)
result.data  # => <User(name='Ronnie')>

Objects <-> List

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

user1 = User(name="Mick", email="mick@stones.com")
user2 = User(name="Keith", email="keith@stones.com")
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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • marshmallow marshmallow是一个用来将复杂的orm对象与python原生数据类型之间相互转换的...
    杨酥饼阅读 11,860评论 2 14
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • JAVA序列化机制的深入研究 对象序列化的最主要的用处就是在传递,和保存对象(object)的时候,保证对象的完整...
    时待吾阅读 10,739评论 0 24
  • Spark SQL, DataFrames and Datasets Guide Overview SQL Dat...
    Joyyx阅读 8,282评论 0 16
  • 版式:以人物为背景,矩形框横跨人物的眼睛,文字排版在矩形框内部 色彩:框釆用浅绿色的。 文字:采用几何体文字,上面...
    递弱阅读 211评论 0 0