相信使用Python做Web開發的朋友都會遇到這樣1個問題,那就是在專案開發中使用模型框架,比如SQLAlchemy、Peewee,我們在做RESTful介面時如何將這些模型序列化為JSON資料。
關於這個問題,跟隔壁那位搞Python的哥們有關係。我不得不佩服這位哥們竟然自己寫了1套ORM框架,而且用起來的那麼遛,不得不讓我汗顏。
但是,在給前端提供介面的時候,如何序列化為JSON資料確實困擾了我們那麼一陣子,畢竟佔據我們很大一部分時間來進行序列化操作。
這裡,我們使用peewee來定義1個簡單的例子來說明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from peewee import SqliteDatabase from peewee import Model, CharField, DateField, BooleanField, ForeignKeyField db = SqliteDatabase('dev.sqlite3') class BaseModel(Model): class Meta: database = db class Person(BaseModel): name = CharField(max_length= 20) birthday = DateField() sex = BooleanField() class Pet(BaseModel): owner = ForeignKeyField(Person, related_name= 'pets') name = CharField(max_length= 10) animal_type = CharField(max_length= 20) |
在這裡我們定義了Person和Pet這2個模型,每個Person可能有1個Pet的寵物。
我們插入一些資料,現在假設我們現在有如下的資料:
1 2 3 4 5 6 7 8 9 10 |
sqlite> select * from person; 1|Bob|1960-01-15|1 2|Grandma|1935-03-01|0 3|Herb|1950-05-05|1 sqlite> select * from pet; 1|1|Kitty|cat 2|3|Fido|dog 3|3|Mittens|cat 4|2|Jack|cat |
現在,我們假設我們介面需要返回的介面是每個使用者的名稱、生日及其對應的寵物的資訊。
我們可以通過連表的方式輕鬆的獲取到我們需要的資料:
1 |
query=Person.select(Person,Pet).join(Pet) |
那麼我們怎麼將這個模型資料轉換為我們需要的JSON資料呢?一般情況下,我們會這樣操作:
1 2 3 4 5 6 7 8 9 10 11 12 |
data = [] for person in query.aggregate_rows(): d={} d['username'] = person.name d['birthday'] = person.birthday d['pet'] = [] for pet in person.pets: o = {} o['name'] = pet.name o['animal_type'] = pet.animal_type d['pet'].append(o) data.append(d) |
最後我們將得到如下的結果:
1 2 3 4 5 6 7 8 9 10 11 |
[{'birthday': datetime.date(1960, 1, 15), 'pet': [{'animal_type': u'cat', 'name': u'Kitty'}], 'username': u'Bob'}, {'birthday': datetime.date(1950, 5, 5), 'pet': [{'animal_type': u'dog', 'name': u'Fido'}, {'animal_type': u'cat', 'name': u'Mittens'}], 'username': u'Herb'}, {'birthday': datetime.date(1935, 3, 1), 'pet': [{'animal_type': u'cat', 'name': u'Jack'}], 'username': u'Grandma'}] |
可以看到,這麼1個簡單的例子,我們已經對序列化操作處理的已經夠嗆的。對於那些更為複雜的模型,我們預計只有哭的份了。
因此,我們希望能找到1個庫可以減輕我們的工作量,於是我們找到了1個marshallow的庫。
下面我們來說說如何使用marshallow來減輕序列化模型的工作量。
主要包括如下2個步驟:
- 定義模式
- 序列化模型
下面我們分別來看看。
定義模式
如果你使用過Flask-RESTful,你應該知道該庫提供了1個marshal_with
的函式。其中我們就需要定義我們給定欄位返回的資料型別,但是Flask-RESTful沒有提供欄位不同返回的操作。
我們通過如下的方式匯入模式及其對應的欄位:
1 |
from marshmallow import Schema, fields |
接下來,我們定義1個繼承自Schema的類,然後定義其對應的欄位:
1 2 3 4 5 6 7 8 |
class PetSchema(Schema): name = fields.String() animal_type = fields.String() class PersonSchema(Schema): name = fields.String(dump_to = 'username') birthday = fields.Date() pets = fields.Nested(PetSchema,dump_to='pet',many=True) |
由於這裡,我們將使用者的name屬性修改為username,因此我們需要在欄位中使用dump_to
引數將其修改為我們需要的欄位。另外,使用者的pet欄位對應的是寵物的資訊,因此我們採用巢狀模式來實現這樣需求。
序列化模型
上面我們已經定義好了我們的模式了,下一步是序列化模型的操作了。
我們可以這樣來操作:
1 |
query=Person.select(Person, Pet).join(Pet) |
接著,我們例項化我們的模式,然後傳入需要序列化的模型:
1 |
person, error = PersonSchema(many = True).dumps(query.aggregate_rows()) |
在這裡,我們呼叫PersonSchema例項的dumps來生成JSON資料,另外它還有1個dump方法用於生成Python物件。由於我們的渲染的資料有多條,因此我們需要在例項化PersonSchema類時傳入關鍵字引數many為True,不然沒有任何資料。
通過這種方式,PersonSchema會檢視它自己的屬性,將資料模型中對應的資料先序列化出來,然後是查詢巢狀模式中的欄位,如果符合對應的名稱則將其序列化出來,最後我們將得到這樣的資料:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
[ { "username": "Bob", "pet": [ { "animal_type": "cat", "name": "Kitty" } ], "birthday": "1960-01-15" }, { "username": "Herb", "pet": [ { "animal_type": "dog", "name": "Fido" }, { "animal_type": "cat", "name": "Mittens" } ], "birthday": "1950-05-05" }, { "username": "Grandma", "pet": [ { "animal_type": "cat", "name": "Jack" } ], "birthday": "1935-03-01" } ] |
可以看到,通過marshallow得到的結果與之前我們編寫的序列化操作的結果是一樣的。
不得不說,marshallow這個庫對於序列化模型其實挺實用的。當然對於複雜的模型,我們需要利用合適的方式將其搜尋出來,不然還是序列化不了的。
參考文章:
https://marshmallow.readthedocs.io/en/latest/quickstart.html