Ember 自定義序列號器

2018-01-06 18:02 更新

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

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

JSONAPISerializer規(guī)范

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

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

1,JSON API文檔

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

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

比如請求/people/123,響應的數據如下:

  1. {
  2. "data": {
  3. "type": "people",
  4. "id": "123",
  5. "attributes": {
  6. "first-name": "Jeff",
  7. "last-name": "Atwood"
  8. }
  9. }
  10. }

如果響應的數據有多條,那么data將是以數組形式返回。

  1. {
  2. "data": [
  3. {
  4. "type": "people",
  5. "id": "123",
  6. "attributes": {
  7. "first-name": "Jeff",
  8. "last-name": "Atwood"
  9. }
  10. },{
  11. "type": "people",
  12. "id": "124",
  13. "attributes": {
  14. "first-name": "chen",
  15. "last-name": "ubuntuvim"
  16. }
  17. }
  18. ]
  19. }

2,拷貝數據

數據有時候并不是請求的主體,如果數據有鏈接。鏈接的關系會放在included下面。

  1. {
  2. "data": {
  3. "type": "articles",
  4. "id": "1",
  5. "attributes": {
  6. "title": "JSON API paints my bikeshed!"
  7. },
  8. "links": {
  9. "self": "http://example.com/articles/1"
  10. },
  11. "relationships": {
  12. "comments": {
  13. "data": [
  14. { "type": "comments", "id": "5" },
  15. { "type": "comments", "id": "12" }
  16. ]
  17. }
  18. }
  19. }],
  20. "included": [{
  21. "type": "comments",
  22. "id": "5",
  23. "attributes": {
  24. "body": "First!"
  25. },
  26. "links": {
  27. "self": "http://example.com/comments/5"
  28. }
  29. }, {
  30. "type": "comments",
  31. "id": "12",
  32. "attributes": {
  33. "body": "I like XML better"
  34. },
  35. "links": {
  36. "self": "http://example.com/comments/12"
  37. }
  38. }]
  39. }

從JSON數據看出,id5comment鏈接是"self": http://example.com/comments/5id12comment鏈接是"self": http://example.com/comments/12。并且這些鏈接是單獨放置included內。

3,自定義序列化器

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

要自定義序列化器首先要定義一個名為application序列化器作為入口。

直接使用命令生成:ember g serializer application

  1. // app/serializers/application.js
  2. import DS from 'ember-data';
  3. export default DS.JSONSerializer.extend({
  4. });

甚至你還可以針對某個模型定義序列化器。比如下面的代碼為post定義了一個專門的序列化器,在前一篇自定義適配器中介紹過如何為一個模型自定義適配器,這個兩個是相關的。

  1. // app/serializers/post.js
  2. import DS from ember-data’;
  3. export default DS.JSONSerializer.extend({
  4. });

如果你想改變發(fā)送到后端的JSON數據格式,你只需重寫serialize回調,在回調中設置數據格式。

比如前端發(fā)送的數據格式是如下結構,

  1. {
  2. "data": {
  3. "attributes": {
  4. "id": "1",
  5. "name": "My Product",
  6. "amount": 100,
  7. "currency": "SEK"
  8. },
  9. "type": "product"
  10. }
  11. }

但是服務器接受的數據結構是下面這種結構:

  1. {
  2. "data": {
  3. "attributes": {
  4. "id": "1",
  5. "name": "My Product",
  6. "cost": {
  7. "amount": 100,
  8. "currency": "SEK"
  9. }
  10. },
  11. "type": "product"
  12. }
  13. }

此時你可以重寫serialize回調。

  1. // app/serializers/application.js
  2. import DS from 'ember-data';
  3. export default DS.JSONSerializer.extend({
  4. serialize: function(snapshot, options) {
  5. var json = this._super(...arguments); // ??
  6. json.data.attributes.cost = {
  7. amount: json.data.attributes.amount,
  8. currency: json.data.attributes.currency
  9. };
  10. delete json.data.attributes.amount;
  11. delete json.data.attributes.currency;
  12. return json;
  13. }
  14. });

那么如果是反過來呢。 如果后端返回的數據格式為:

  1. {
  2. "data": {
  3. "attributes": {
  4. "id": "1",
  5. "name": "My Product",
  6. "cost": {
  7. "amount": 100,
  8. "currency": "SEK"
  9. }
  10. },
  11. "type": "product"
  12. }
  13. }

但是前端需要的格式是:

  1. {
  2. "data": {
  3. "attributes": {
  4. "id": "1",
  5. "name": "My Product",
  6. "amount": 100,
  7. "currency": "SEK"
  8. },
  9. "type": "product"
  10. }
  11. }

此時你可以重寫回調方法normalizeResponsenormalize,在方法里設置數據格式:

  1. // app/serializers/application.js
  2. import DS from 'ember-data';
  3. export default DS.JSONSerializer.extend({
  4. normalizeResponse: function(store, primaryModelClass, payload, id, requestType) {
  5. payload.data.attributes.amount = payload.data.attributes.cost.amount;
  6. payload.data.attributes.currency = payload.data.attributes.cost.currency;
  7. delete payload.data.attributes.cost;
  8. return this._super(...arguments);
  9. }
  10. });

4,數據ID屬性

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

  1. // app/serializers/application.js
  2. import DS from 'ember-data';
  3. export default DS.JSONSerializer.extend({
  4. primatyKey: '__id'
  5. });

把數據主鍵名修改為__id。

5,屬性名

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

  1. // app/serializers/application.js
  2. import DS from 'ember-data';
  3. export default DS.JSONSerializer.extend({
  4. keyForAttributes: function(attr) {
  5. return Ember.String.underscore(attr);
  6. }
  7. });

6,指定屬性名的別名

如果你想模型數據被序列化、反序列化時指定模型屬性的別名,直接在序列化器中使用attrs屬性指定即可。

  1. // app/models/person.js
  2. export default DS.Model.extend({
  3. lastName: DS.attr(‘string’)
  4. });

指定序列化、反序列化屬性別名:

  1. // app/serializers/application.js
  2. import DS from 'ember-data';
  3. export default DS.JSONSerializer.extend({
  4. attrs: {
  5. lastName: lastNameOfPerson
  6. }
  7. });

指定模型屬性別名為lastNameOfPerson。

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

一個模型通過ID引用另一個模型。比如有兩個模型存在一對多關系:

  1. // app/models/post.js
  2. export default DS.Model.extend({
  3. comments: DS.hasMany(‘comment’, { async: true });
  4. });

序列化后JSON數據格式如下,其中關聯(lián)關系通過一個存放ID屬性值的數組實現。

  1. {
  2. "data": {
  3. "type": "posts",
  4. "id": "1",
  5. "relationships": {
  6. "comments": {
  7. "data": [
  8. { "type": "comments", "id": "5" },
  9. { "type": "comments", "id": "12" }
  10. ]
  11. }
  12. }
  13. }
  14. }

可見,有兩個comment關聯(lián)到一個post上。 如果是belongsTo關系的,JSON結構與hadMany關系相差不大。

  1. {
  2. "data": {
  3. "type": "comment",
  4. "id": "1",
  5. "relationships": {
  6. "original-post": {
  7. "data": { "type": "post", "id": "5" },
  8. }
  9. }
  10. }
  11. }

id1comment關聯(lián)了ID5post。

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

在某些情況下,Ember內置的屬性類型(string、number、booleandate)還是不夠用的。比如,服務器返回的是非標準的數據格式時。

Ember Data可以注冊新的JSON轉換器去格式化數據,可用直接使用命令創(chuàng)建:ember g transform coordinate-point

  1. // app/transforms/coordinate-point.js
  2. import DS from 'ember-data';
  3. export default DS.Transform.extend({
  4. deserialize: function(v) {
  5. return [v.get('x'), v.get('y')];
  6. },
  7. serialize: function(v) {
  8. return Ember.create({ x: v[0], y: v[1]});
  9. }
  10. });

定義一個復合屬性類型,這個類型由兩個屬性構成,形成一個坐標。

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

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

  1. {
  2. cursor: {
  3. position: [4, 9]
  4. }
  5. }

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

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

9,JSONSerializer

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

  1. // app/serializer/application.js
  2. export default DS.JSONSerializer.extend({
  3. // ...
  4. });

10,EMBEDDEDRECORDMIXIN

盡管Ember Data鼓勵你拷貝模型關聯(lián)關系,但有時候在處理遺留API時,你會發(fā)現你需要處理的JSON中嵌入了其他模型的關聯(lián)關系。不過EmbeddedRecordsMixin可以幫你解決這個問題。

比如post中包含了一個author記錄。

  1. {
  2. "id": "1",
  3. "title": "Rails is omakase",
  4. "tag": "rails",
  5. "authors": [
  6. {
  7. "id": "2",
  8. "name": "Steve"
  9. }
  10. ]
  11. }

你可以定義里的模型關聯(lián)關系如下:

  1. // app/serializers/post.js
  2. export default DS.JSONSerialier.extend(DS.EmbeddedRecordsMixin, {
  3. attrs: {
  4. author: {
  5. serialize: records’,
  6. deserialize: records
  7. }
  8. }
  9. });

如果你發(fā)生對象本身需要序列化與反序列化嵌入的關系,你可以使用屬性embedded設置。

  1. // app/serializers/post.js
  2. export default DS.JSONSerialier.extend(DS.EmbeddedRecordsMixin, {
  3. attrs: {
  4. author: { embedded: always }
  5. }
  6. });

序列化與反序列化設置有3個關鍵字:

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

例如,你可能會發(fā)現你想讀一個嵌入式記錄提取時一個JSON有效載荷只包括關系的身份在序列化記錄。這可能是使用serialize: ids。你也可以選擇通過設置序列化的關系 serialize: false

  1. export default DS.JSONSerializer.extend(DS.EmbeddedRecordsMixin, {
  2. attrs: {
  3. author: {
  4. serialize: false,
  5. deserialize: 'records'
  6. },
  7. comments: {
  8. deserialize: 'records',
  9. serialize: 'ids'
  10. }
  11. }
  12. });

11,EMBEDDEDRECORDSMIXIN 默認設置

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

  1. belongsTo:{serialize: id’, deserialize: id }
  2. hasMany: { serialize: false, deserialize: ids }

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

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

  1. normalizeResponse
  2. serialize
  3. normalize

知道規(guī)范化JSON數據對Ember Data來說是非常重要的,如果模型屬性名不符合Ember Data規(guī)范這些屬性值將不會自動更新。如果返回的數據沒有在模型中指定那么這些數據將會被忽略。比如下面的模型定義,this.store.push()方法接受的格式為第二段代碼所示。

  1. // app/models/post.js
  2. import DS from 'ember-data';
  3. export default DS.Model.extend({
  4. title: DS.attr(‘string’),
  5. tag: DS.attr(‘string’),
  6. comments: hasMany(‘comment’, { async: true }),
  7. relatedPosts: hasMany(‘post’)
  8. });
  1. {
  2. data: {
  3. id: "1",
  4. type: 'post',
  5. attributes: {
  6. title: "Rails is omakase",
  7. tag: "rails",
  8. },
  9. relationships: {
  10. comments: {
  11. data: [{ id: "1", type: 'comment' },
  12. { id: "2", type: 'comment' }],
  13. },
  14. relatedPosts: {
  15. data: {
  16. related: "/api/v1/posts/1/related-posts/"
  17. }
  18. }
  19. }
  20. }

每個序列化記錄必須按照這個格式要正確地轉換成Ember Data記錄。
本篇的內容難度很大,屬于高級主題的內容!如果暫時理解不來不要緊,你可以先使用firebase構建項目,等你熟悉了整個Ember流程以及數據是如何交互之后再回過頭看這篇和上一篇Ember.js 入門指南之四十四自定義適配器,這樣就不至于難以理解了!!
到本篇為止,有關Ember的基礎知識全部介紹完畢?。?!從2015-08-26開始到現在剛好2個月,原計劃是用3個月時間完成的,提前了一個月,歸其原因是后面的內容難度大,理解偏差大!文章質量也不好,感覺時間比較倉促,說以節(jié)省了很多時間?。?em>本篇是重新整理發(fā)表的,原始版博文發(fā)布的時候Ember還是2.0版本,現在已經是2.5了??!)
介紹來打算介紹APPLICATION CONCERNS和TESTING這兩章!也有可能把舊版的Ember todomvc案例改成Ember2.0版本的,正好可以拿來練練手速?。?!
很慶幸的是目標:把舊版的Ember todomvc案例改成Ember2.0版本的,也完成了!?。〔⑶覕U展了很多功能,有關代碼情況todos v2,歡迎讀者fork學習!如果覺得有用就給我一個star吧??!謝謝?。?!


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

以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號