Ember 自定義序列號器

2018-01-06 18:02 更新

Ember應(yīng)用中,序列化器會格式化與后臺交互的數(shù)據(jù),包括發(fā)送和接收的數(shù)據(jù)。默認(rèn)情況下會使用JSON API序列化數(shù)據(jù)。如果你的后端使用不同的格式,Ember Data允許你自定義序列化器或者定義一個完全不同的序列化器。

Ember Data內(nèi)置了三個序列化器。JSONAPISerializer是默認(rèn)的序列化器,用與處理后端的JSON API。JSONSerializer是一個簡單的序列化器,用與處理單個JSON對象或者是處理記錄數(shù)組。RESTSerializer是一個復(fù)雜的序列化器,支持側(cè)面加載,在Ember Data2.0之前是默認(rèn)的序列化器。

JSONAPISerializer規(guī)范

當(dāng)你向服務(wù)器請求數(shù)據(jù)時,JSONSerializer會把服務(wù)器返回的數(shù)據(jù)當(dāng)做是符合下列規(guī)范的JSON數(shù)據(jù)。

注意:特別是項(xiàng)目使用的是自定義適配器的時候,后臺返回的數(shù)據(jù)格式必須符合JSOP API規(guī)范,否則無法實(shí)現(xiàn)數(shù)據(jù)的CRUD操作,Ember就無法解析數(shù)據(jù),關(guān)于自定義適配器這點(diǎn)的知識請看上一篇Ember.js 入門指南之四十四自定義適配器,在文章中有詳細(xì)的介紹自定義適配器和自定義序列化器是息息相關(guān)的。

1,JSON API文檔

JSONSerializer期待后臺返回的是一個符合JSON API規(guī)范和約定的JSON文檔。比如下面的JSON數(shù)據(jù),這些數(shù)據(jù)的格式是這樣的:

  1. type指定model的名稱
  2. 屬性名稱使用中劃線分隔

比如請求/people/123,響應(yīng)的數(shù)據(jù)如下:

{
  "data": {
    "type": "people",
    "id": "123",
    "attributes": {
      "first-name": "Jeff",
      "last-name": "Atwood"
    }
  }
}

如果響應(yīng)的數(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"
        }
    }
  ]
}

2,拷貝數(shù)據(jù)

數(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ù)看出,id5comment鏈接是"self": http://example.com/comments/5。id12comment鏈接是"self": http://example.com/comments/12。并且這些鏈接是單獨(dú)放置included內(nèi)。

3,自定義序列化器

Ember Data默認(rèn)的序列化器是JSONAPISerializer,但是你也可以自定義序列化器覆蓋默認(rèn)的序列化器。

要自定義序列化器首先要定義一個名為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"
  }
}

但是服務(wù)器接受的數(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)方法normalizeResponsenormalize,在方法里設(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);
    }
});

4,數(shù)據(jù)ID屬性

每一條數(shù)據(jù)都有一個唯一值作為ID,默認(rèn)情況下Ember會為每個模型加上一個名為id的屬性。如果你想改為其他名稱,你可以在序列化器中指定。

//  app/serializers/application.js


import DS from 'ember-data';


export default DS.JSONSerializer.extend({
    primatyKey: '__id'
});

把數(shù)據(jù)主鍵名修改為__id。

5,屬性名

Ember Data約定的屬性名是駝峰式的命名方式,但是序列化器卻期望的是中劃線分隔的命名方式,不過Ember會自動轉(zhuǎn)換,不需要開發(fā)者手動指定。然而,如果你想修改這種默認(rèn)的方式也是可以的,只需在序列化器中使用屬性keyForAttributes指定你喜歡的分隔方式即可。比如下面的代碼把序列號的屬性名稱改為以下劃線分隔:

//  app/serializers/application.js


import DS from 'ember-data';


export default DS.JSONSerializer.extend({
  keyForAttributes: function(attr) {
    return Ember.String.underscore(attr);  
  }
});

6,指定屬性名的別名

如果你想模型數(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。

7,模型之間的關(guān)聯(lián)關(guān)系

一個模型通過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ù)組實(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" },
      }
    }
  }
}

id1comment關(guān)聯(lián)了ID5post。

8,自定義轉(zhuǎn)換規(guī)則

在某些情況下,Ember內(nèi)置的屬性類型(string、numberboolean、date)還是不夠用的。比如,服務(wù)器返回的是非標(biāo)準(zhǔn)的數(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]});
  }
});

定義一個復(fù)合屬性類型,這個類型由兩個屬性構(gòu)成,形成一個坐標(biāo)。

//  app/models/curor.js
import DS from 'ember-data';
export default DS.Model.extend({
  position: DS.attr(‘coordinate-point’)
});

自定義的屬性類型使用方式與普通類型一致,直接作為attr方法的參數(shù)。最后當(dāng)我們接受到服務(wù)返回的數(shù)據(jù)形如下面的代碼所示:

{
  cursor: {
    position: [4, 9]
  }
}

加載模型實(shí)例時仍然作為一個普通對象加載。仍然可以使用.操作獲取屬性值。

var cursor = this.store.findRecord(‘cursor’, 1);
cursor.get(‘position.x’);  //  => 4
cursor.get(‘position.y’);  //  => 9

9,JSONSerializer

并不是所有的API都遵循JSONAPISerializer約定通過數(shù)據(jù)命名空間和拷貝關(guān)系記錄。比如系統(tǒng)遺留問題,原先的API返回的只是簡單的JSON格式并不是JSONAPISerializer約定的格式,此時你可以自定義序列化器去適配舊接口。并且可以同時兼容使用RESTAdapter去序列號這些簡單的JSON數(shù)據(jù)。

//  app/serializer/application.js


export default DS.JSONSerializer.extend({
  // ...
});

10,EMBEDDEDRECORDMIXIN

盡管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)鍵字:

  1. records 用于標(biāo)記全部的記錄都是序列化與反序列化的
  2. ids 用于標(biāo)記僅僅序列化與反序列化記錄的id
  3. false 用于標(biāo)記記錄不需要序列化與反序列化

例如,你可能會發(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'
    }
  }
});

11,EMBEDDEDRECORDSMIXIN 默認(rèn)設(shè)置

如果你沒有重寫attrs去指定模型的關(guān)聯(lián)關(guān)系,那么EmbeddedRecordsMixin會有如下的默認(rèn)行為:

belongsTo:{serialize: ‘id’, deserialize: ‘id’ }
hasMany: { serialize: false, deserialize: ‘ids’ }

12,創(chuàng)作序列化器

如果項(xiàng)目需要自定義序列化器,Ember推薦擴(kuò)展JSONAIPSerializer或者JSONSerializer來實(shí)現(xiàn)你的需求。但是,如果你想完全創(chuàng)建一個全新的與JSONAIPSerializer、JSONSerializer都不一樣的序列化器你可以擴(kuò)展DS.Serializer類,但是你必須要實(shí)現(xiàn)下面三個方法:

  1. normalizeResponse
  2. serialize
  3. normalize

知道規(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)建項(xiàng)目,等你熟悉了整個Ember流程以及數(shù)據(jù)是如何交互之后再回過頭看這篇和上一篇Ember.js 入門指南之四十四自定義適配器,這樣就不至于難以理解了!!
到本篇為止,有關(guān)Ember的基礎(chǔ)知識全部介紹完畢?。?!從2015-08-26開始到現(xiàn)在剛好2個月,原計(jì)劃是用3個月時間完成的,提前了一個月,歸其原因是后面的內(nèi)容難度大,理解偏差大!文章質(zhì)量也不好,感覺時間比較倉促,說以節(jié)省了很多時間?。?em>本篇是重新整理發(fā)表的,原始版博文發(fā)布的時候Ember還是2.0版本,現(xiàn)在已經(jīng)是2.5了!!)
介紹來打算介紹APPLICATION CONCERNS和TESTING這兩章!也有可能把舊版的Ember todomvc案例改成Ember2.0版本的,正好可以拿來練練手速?。?!
很慶幸的是目標(biāo):把舊版的Ember todomvc案例改成Ember2.0版本的,也完成了!?。〔⑶覕U(kuò)展了很多功能,有關(guān)代碼情況todos v2,歡迎讀者fork學(xué)習(xí)!如果覺得有用就給我一個star吧?。≈x謝?。?!


博文完整代碼放在Github(博文經(jīng)過多次修改,博文上的代碼與github代碼可能有出入,不過影響不大?。绻阌X得博文對你有點(diǎn)用,請?jiān)趃ithub項(xiàng)目上給我點(diǎn)個star吧。您的肯定對我來說是最大的動力??!

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號