在Ember應用中,序列化器會格式化與后臺交互的數(shù)據(jù),包括發(fā)送和接收的數(shù)據(jù)。默認情況下會使用JSON API序列化數(shù)據(jù)。如果你的后端使用不同的格式,Ember Data允許你自定義序列化器或者定義一個完全不同的序列化器。
Ember Data內(nèi)置了三個序列化器。JSONAPISerializer是默認的序列化器,用與處理后端的JSON API。JSONSerializer是一個簡單的序列化器,用與處理單個JSON對象或者是處理記錄數(shù)組。RESTSerializer是一個復雜的序列化器,支持側(cè)面加載,在Ember Data2.0之前是默認的序列化器。
當你向服務器請求數(shù)據(jù)時,JSONSerializer會把服務器返回的數(shù)據(jù)當做是符合下列規(guī)范的JSON數(shù)據(jù)。
注意:特別是項目使用的是自定義適配器的時候,后臺返回的數(shù)據(jù)格式必須符合JSOP API規(guī)范,否則無法實現(xiàn)數(shù)據(jù)的CRUD操作,Ember就無法解析數(shù)據(jù),關(guān)于自定義適配器這點的知識請看上一篇Ember.js 入門指南之四十四自定義適配器,在文章中有詳細的介紹自定義適配器和自定義序列化器是息息相關(guān)的。
JSONSerializer期待后臺返回的是一個符合JSON API規(guī)范和約定的JSON文檔。比如下面的JSON數(shù)據(jù),這些數(shù)據(jù)的格式是這樣的:
比如請求/people/123
,響應的數(shù)據(jù)如下:
{
"data": {
"type": "people",
"id": "123",
"attributes": {
"first-name": "Jeff",
"last-name": "Atwood"
}
}
}
如果響應的數(shù)據(jù)有多條,那么data
將是以數(shù)組形式返回。
{
"data": [
{
"type": "people",
"id": "123",
"attributes": {
"first-name": "Jeff",
"last-name": "Atwood"
}
},{
"type": "people",
"id": "124",
"attributes": {
"first-name": "chen",
"last-name": "ubuntuvim"
}
}
]
}
數(shù)據(jù)有時候并不是請求的主體,如果數(shù)據(jù)有鏈接。鏈接的關(guān)系會放在included
下面。
{
"data": {
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON API paints my bikeshed!"
},
"links": {
"self": "http://example.com/articles/1"
},
"relationships": {
"comments": {
"data": [
{ "type": "comments", "id": "5" },
{ "type": "comments", "id": "12" }
]
}
}
}],
"included": [{
"type": "comments",
"id": "5",
"attributes": {
"body": "First!"
},
"links": {
"self": "http://example.com/comments/5"
}
}, {
"type": "comments",
"id": "12",
"attributes": {
"body": "I like XML better"
},
"links": {
"self": "http://example.com/comments/12"
}
}]
}
從JSON數(shù)據(jù)看出,id
為5
的comment
鏈接是"self": http://example.com/comments/5
。id
為12
的comment
鏈接是"self": http://example.com/comments/12
。并且這些鏈接是單獨放置included
內(nèi)。
Ember Data默認的序列化器是JSONAPISerializer,但是你也可以自定義序列化器覆蓋默認的序列化器。
要自定義序列化器首先要定義一個名為application
序列化器作為入口。
直接使用命令生成:ember g serializer application
// app/serializers/application.js
import DS from 'ember-data';
export default DS.JSONSerializer.extend({
});
甚至你還可以針對某個模型定義序列化器。比如下面的代碼為post
定義了一個專門的序列化器,在前一篇自定義適配器中介紹過如何為一個模型自定義適配器,這個兩個是相關(guān)的。
// app/serializers/post.js
import DS from ‘ember-data’;
export default DS.JSONSerializer.extend({
});
如果你想改變發(fā)送到后端的JSON數(shù)據(jù)格式,你只需重寫serialize
回調(diào),在回調(diào)中設(shè)置數(shù)據(jù)格式。
比如前端發(fā)送的數(shù)據(jù)格式是如下結(jié)構(gòu),
{
"data": {
"attributes": {
"id": "1",
"name": "My Product",
"amount": 100,
"currency": "SEK"
},
"type": "product"
}
}
但是服務器接受的數(shù)據(jù)結(jié)構(gòu)是下面這種結(jié)構(gòu):
{
"data": {
"attributes": {
"id": "1",
"name": "My Product",
"cost": {
"amount": 100,
"currency": "SEK"
}
},
"type": "product"
}
}
此時你可以重寫serialize
回調(diào)。
// app/serializers/application.js
import DS from 'ember-data';
export default DS.JSONSerializer.extend({
serialize: function(snapshot, options) {
var json = this._super(...arguments); // ??
json.data.attributes.cost = {
amount: json.data.attributes.amount,
currency: json.data.attributes.currency
};
delete json.data.attributes.amount;
delete json.data.attributes.currency;
return json;
}
});
那么如果是反過來呢。 如果后端返回的數(shù)據(jù)格式為:
{
"data": {
"attributes": {
"id": "1",
"name": "My Product",
"cost": {
"amount": 100,
"currency": "SEK"
}
},
"type": "product"
}
}
但是前端需要的格式是:
{
"data": {
"attributes": {
"id": "1",
"name": "My Product",
"amount": 100,
"currency": "SEK"
},
"type": "product"
}
}
此時你可以重寫回調(diào)方法normalizeResponse
或normalize
,在方法里設(shè)置數(shù)據(jù)格式:
// app/serializers/application.js
import DS from 'ember-data';
export default DS.JSONSerializer.extend({
normalizeResponse: function(store, primaryModelClass, payload, id, requestType) {
payload.data.attributes.amount = payload.data.attributes.cost.amount;
payload.data.attributes.currency = payload.data.attributes.cost.currency;
delete payload.data.attributes.cost;
return this._super(...arguments);
}
});
每一條數(shù)據(jù)都有一個唯一值作為ID
,默認情況下Ember會為每個模型加上一個名為id
的屬性。如果你想改為其他名稱,你可以在序列化器中指定。
// app/serializers/application.js
import DS from 'ember-data';
export default DS.JSONSerializer.extend({
primatyKey: '__id'
});
把數(shù)據(jù)主鍵名修改為__id
。
Ember Data約定的屬性名是駝峰式的命名方式,但是序列化器卻期望的是中劃線分隔的命名方式,不過Ember會自動轉(zhuǎn)換,不需要開發(fā)者手動指定。然而,如果你想修改這種默認的方式也是可以的,只需在序列化器中使用屬性keyForAttributes
指定你喜歡的分隔方式即可。比如下面的代碼把序列號的屬性名稱改為以下劃線分隔:
// app/serializers/application.js
import DS from 'ember-data';
export default DS.JSONSerializer.extend({
keyForAttributes: function(attr) {
return Ember.String.underscore(attr);
}
});
如果你想模型數(shù)據(jù)被序列化、反序列化時指定模型屬性的別名,直接在序列化器中使用attrs
屬性指定即可。
// app/models/person.js
export default DS.Model.extend({
lastName: DS.attr(‘string’)
});
指定序列化、反序列化屬性別名:
// app/serializers/application.js
import DS from 'ember-data';
export default DS.JSONSerializer.extend({
attrs: {
lastName: ‘lastNameOfPerson’
}
});
指定模型屬性別名為lastNameOfPerson
。
一個模型通過ID
引用另一個模型。比如有兩個模型存在一對多關(guān)系:
// app/models/post.js
export default DS.Model.extend({
comments: DS.hasMany(‘comment’, { async: true });
});
序列化后JSON數(shù)據(jù)格式如下,其中關(guān)聯(lián)關(guān)系通過一個存放ID
屬性值的數(shù)組實現(xiàn)。
{
"data": {
"type": "posts",
"id": "1",
"relationships": {
"comments": {
"data": [
{ "type": "comments", "id": "5" },
{ "type": "comments", "id": "12" }
]
}
}
}
}
可見,有兩個comment
關(guān)聯(lián)到一個post
上。
如果是belongsTo
關(guān)系的,JSON結(jié)構(gòu)與hadMany
關(guān)系相差不大。
{
"data": {
"type": "comment",
"id": "1",
"relationships": {
"original-post": {
"data": { "type": "post", "id": "5" },
}
}
}
}
id
為1
的comment
關(guān)聯(lián)了ID
為5
的post
。
在某些情況下,Ember內(nèi)置的屬性類型(string
、number
、boolean
、date
)還是不夠用的。比如,服務器返回的是非標準的數(shù)據(jù)格式時。
Ember Data可以注冊新的JSON轉(zhuǎn)換器去格式化數(shù)據(jù),可用直接使用命令創(chuàng)建:ember g transform coordinate-point
// app/transforms/coordinate-point.js
import DS from 'ember-data';
export default DS.Transform.extend({
deserialize: function(v) {
return [v.get('x'), v.get('y')];
},
serialize: function(v) {
return Ember.create({ x: v[0], y: v[1]});
}
});
定義一個復合屬性類型,這個類型由兩個屬性構(gòu)成,形成一個坐標。
// app/models/curor.js
import DS from 'ember-data';
export default DS.Model.extend({
position: DS.attr(‘coordinate-point’)
});
自定義的屬性類型使用方式與普通類型一致,直接作為attr
方法的參數(shù)。最后當我們接受到服務返回的數(shù)據(jù)形如下面的代碼所示:
{
cursor: {
position: [4, 9]
}
}
加載模型實例時仍然作為一個普通對象加載。仍然可以使用.
操作獲取屬性值。
var cursor = this.store.findRecord(‘cursor’, 1);
cursor.get(‘position.x’); // => 4
cursor.get(‘position.y’); // => 9
并不是所有的API都遵循JSONAPISerializer約定通過數(shù)據(jù)命名空間和拷貝關(guān)系記錄。比如系統(tǒng)遺留問題,原先的API返回的只是簡單的JSON格式并不是JSONAPISerializer約定的格式,此時你可以自定義序列化器去適配舊接口。并且可以同時兼容使用RESTAdapter去序列號這些簡單的JSON數(shù)據(jù)。
// app/serializer/application.js
export default DS.JSONSerializer.extend({
// ...
});
盡管Ember Data鼓勵你拷貝模型關(guān)聯(lián)關(guān)系,但有時候在處理遺留API時,你會發(fā)現(xiàn)你需要處理的JSON中嵌入了其他模型的關(guān)聯(lián)關(guān)系。不過EmbeddedRecordsMixin
可以幫你解決這個問題。
比如post
中包含了一個author
記錄。
{
"id": "1",
"title": "Rails is omakase",
"tag": "rails",
"authors": [
{
"id": "2",
"name": "Steve"
}
]
}
你可以定義里的模型關(guān)聯(lián)關(guān)系如下:
// app/serializers/post.js
export default DS.JSONSerialier.extend(DS.EmbeddedRecordsMixin, {
attrs: {
author: {
serialize: ‘records’,
deserialize: ‘records’
}
}
});
如果你發(fā)生對象本身需要序列化與反序列化嵌入的關(guān)系,你可以使用屬性embedded
設(shè)置。
// app/serializers/post.js
export default DS.JSONSerialier.extend(DS.EmbeddedRecordsMixin, {
attrs: {
author: { embedded: ‘a(chǎn)lways’ }
}
});
序列化與反序列化設(shè)置有3個關(guān)鍵字:
records
用于標記全部的記錄都是序列化與反序列化的ids
用于標記僅僅序列化與反序列化記錄的idfalse
用于標記記錄不需要序列化與反序列化
例如,你可能會發(fā)現(xiàn)你想讀一個嵌入式記錄提取時一個JSON有效載荷只包括關(guān)系的身份在序列化記錄。這可能是使用serialize: ids
。你也可以選擇通過設(shè)置序列化的關(guān)系 serialize: false
。
export default DS.JSONSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
author: {
serialize: false,
deserialize: 'records'
},
comments: {
deserialize: 'records',
serialize: 'ids'
}
}
});
如果你沒有重寫attrs
去指定模型的關(guān)聯(lián)關(guān)系,那么EmbeddedRecordsMixin
會有如下的默認行為:
belongsTo:{serialize: ‘id’, deserialize: ‘id’ }
hasMany: { serialize: false, deserialize: ‘ids’ }
如果項目需要自定義序列化器,Ember推薦擴展JSONAIPSerializer或者JSONSerializer來實現(xiàn)你的需求。但是,如果你想完全創(chuàng)建一個全新的與JSONAIPSerializer、JSONSerializer都不一樣的序列化器你可以擴展DS.Serializer
類,但是你必須要實現(xiàn)下面三個方法:
知道規(guī)范化JSON數(shù)據(jù)對Ember Data來說是非常重要的,如果模型屬性名不符合Ember Data規(guī)范這些屬性值將不會自動更新。如果返回的數(shù)據(jù)沒有在模型中指定那么這些數(shù)據(jù)將會被忽略。比如下面的模型定義,this.store.push()
方法接受的格式為第二段代碼所示。
// app/models/post.js
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr(‘string’),
tag: DS.attr(‘string’),
comments: hasMany(‘comment’, { async: true }),
relatedPosts: hasMany(‘post’)
});
{
data: {
id: "1",
type: 'post',
attributes: {
title: "Rails is omakase",
tag: "rails",
},
relationships: {
comments: {
data: [{ id: "1", type: 'comment' },
{ id: "2", type: 'comment' }],
},
relatedPosts: {
data: {
related: "/api/v1/posts/1/related-posts/"
}
}
}
}
每個序列化記錄必須按照這個格式要正確地轉(zhuǎn)換成Ember Data記錄。
本篇的內(nèi)容難度很大,屬于高級主題的內(nèi)容!如果暫時理解不來不要緊,你可以先使用firebase構(gòu)建項目,等你熟悉了整個Ember流程以及數(shù)據(jù)是如何交互之后再回過頭看這篇和上一篇Ember.js 入門指南之四十四自定義適配器,這樣就不至于難以理解了!!
到本篇為止,有關(guān)Ember的基礎(chǔ)知識全部介紹完畢?。?!從2015-08-26開始到現(xiàn)在剛好2個月,原計劃是用3個月時間完成的,提前了一個月,歸其原因是后面的內(nèi)容難度大,理解偏差大!文章質(zhì)量也不好,感覺時間比較倉促,說以節(jié)省了很多時間?。?em>本篇是重新整理發(fā)表的,原始版博文發(fā)布的時候Ember還是2.0版本,現(xiàn)在已經(jīng)是2.5了!!)
介紹來打算介紹APPLICATION CONCERNS和TESTING這兩章!也有可能把舊版的Ember todomvc案例改成Ember2.0版本的,正好可以拿來練練手速!??!
很慶幸的是目標:把舊版的Ember todomvc案例改成Ember2.0版本的,也完成了?。。〔⑶覕U展了很多功能,有關(guān)代碼情況todos v2,歡迎讀者fork學習!如果覺得有用就給我一個star
吧??!謝謝!??!
博文完整代碼放在Github(博文經(jīng)過多次修改,博文上的代碼與github代碼可能有出入,不過影響不大!),如果你覺得博文對你有點用,請在github項目上給我點個star
吧。您的肯定對我來說是最大的動力!!
更多建議: