Ember.js 入门指南——自定义序列号器

江亮
2023-12-01

       Ember应用中,序列化器会格式化与后台交互的数据,包括发送和接收的数据。默认情况下会使用JSON API序列化数据。如果你的后端使用不同的格式,Ember Data允许你自定义序列化器或者定义一个完全不同的序列化器。

       Ember Data内置了三个序列化器。JSONAPISerializer是默认的序列化器,用与处理后端的JSON APIJSONSerializer是一个简单的序列化器,用与处理单个JSON对象或者是处理记录数组。RESTSerializer是一个复杂的序列化器,支持侧面加载,在Ember Data2.0之前是默认的序列化器。

 

JSONAPISerializer规范

       当你向服务器请求数据时,JSONSerializer会把服务器返回的数据当做是符合下列规范的JSON数据。

1JSON API文档

       JSONSerializer期待后台返回的是一个符合JSON API规范和约定的JSON文档。比如下面的JSON数据,这些数据的格式是这样的:

1,type指定model的名称

2,属性名称使用中划线分隔

比如请求/people/123,响应的数据如下:

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

       如果响应的数据有多条,那么data将是以数组形式返回。

{
  "data": [
     {
           "type": "people",
           "id": "123",
           "attributes": {
             "first-name": "Jeff",
             "last-name": "Atwood"
           }
       },{
           "type": "people",
           "id": "124",
           "attributes": {
             "first-name": "chen",
             "last-name": "ubuntuvim"
           }
       }
  ]
}

 

2,拷贝数据

       数据有时候并不是请求的主体,如果数据有链接。链接的关系会放在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数据看出,id5comment链接是"self": http://example.com/comments/5id12comment链接是"self": http://example.com/comments/12。并且这些链接是单独放置included内。

 

3,自定义序列化器

       Ember Data默认的序列化器是JSONAPISerializer,但是你也可以自定义序列化器覆盖默认的序列化器。

       要自定义序列化器首先要定义一个名为application序列化器作为入口。

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

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

 

       甚至你还可以针对某个model定义序列化器。比如下面的代码为post定义了一个专门的序列化器。

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

 

       如果你想改变发送到后端的JSON数据格式,你只需重写serialize回调,在回调中设置数据格式。

       比如前端发送的数据格式是如下结构,

{
  "data": {
    "attributes": {
      "id": "1",
      "name": "My Product",
      "amount": 100,
      "currency": "SEK"
    },
    "type": "product"
  }
}

但是服务器接受的数据结构是下面这种结构:

{
  "data": {
    "attributes": {
      "id": "1",
      "name": "My Product",
      "cost": {
        "amount": 100,
        "currency": "SEK"
      }
    },
    "type": "product"
  }
}

 

此时你可以重写serialize回调。

//  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;
       }
});

 

       那么如果是反过来呢。

如果后端返回的数据格式为:

{
  "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"
  }
}

 

此时你可以重写回调方法normalizeResponsenormalize,在方法里设置数据格式:

//  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,数据ID属性

       每一条数据都有一个唯一值作为ID,默认情况下Ember会为每个model加上一个名为id的属性。如果你想改为其他名称,你可以在序列化器中指定。

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

       把数据主键名修改为“__id”

 

5,属性名

       Ember Data约定的属性名是驼峰式的命名方式,但是序列化器却期望的是中划线分隔的命名方式,不过Ember会自动转换,不需要开发者手动指定。然而,如果你想修改这种默认的方式也是可以的,只需在序列化器中使用属性keyForAttributes指定你喜欢的分隔方式即可。比如下面的代码把序列号的属性名称改为以下划线分隔:

//  app/serializers/application.js
 
import DS from 'ember-data';
 
export default DS.JSONSerializer.extend({
  keyForAttributes: function(attr) {
    return Ember.String.underscore(attr); 
  }
});
6,指定属性名的别名

       如果你想model数据被序列化、反序列化时指定model属性的别名,直接在序列化器中使用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’
  }
});

       指定model属性别名为lastNameOfPerson

 

7model之间的关联关系

       一个model通过ID引用另一个model。比如有两个model存在一对多关系:

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

 

序列化后JSON数据格式如下,其中关联关系通过一个存放ID属性值的数组实现。

{
  "data": {
    "type": "posts",
    "id": "1",
    "relationships": {
      "comments": {
        "data": [
          { "type": "comments", "id": "5" },
          { "type": "comments", "id": "12" }
        ]
      }
    }
  }
}

       可见,有两个comment关联到一个post上。

如果是belongsTo关系的,JSON结构与hadMany关系相差不大。

{
  "data": {
    "type": "comment",
    "id": "1",
    "relationships": {
      "original-post": {
        "data": { "type": "post", "id": "5" },
      }
    }
  }
}

ID1comment关联了ID5post

 

8,自定义转换规则

       在某些情况下,Ember内置的属性类型(stringnumberbooleandate)还是不够用的。比如,服务器返回的是非标准的数据格式时。

       Ember Data可以注册新的JSON转换器去格式化数据,可用直接使用命令创建: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]});
  }
});

       定义一个复合属性类型,这个类型由两个属性构成,形成一个坐标。

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

自定义的属性类型使用方式与普通类型一致,直接作为attr方法的参数。最后当我们接受到服务返回的数据形如下面的代码所示:

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

       加载model实例时仍然作为一个普通对象加载。仍然可以使用“.”获取属性值。

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

 

9JSONSerializer

       并不是所有的API都遵循JSONAPISerializer约定通过数据命名空间和拷贝关系记录。比如系统遗留问题,原先的API返回的只是简单的JSON格式并不是JSONAPISerializer约定的格式,此时你可以自定义序列化器去适配旧接口。并且可以同时兼容使用RESTAdapter去序列号这些简单的JSON数据。

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

 

10EMBEDDEDRECORDMIXIN

       尽管Ember Data鼓励你拷贝model关联关系,但有时候在处理遗留API时,你会发现你需要处理的JSON中嵌入了其他model的关联关系。不过EmbeddedRecordsMixin可以帮你解决这个问题。

       比如post中包含了一个author记录。

{
    "id": "1",
    "title": "Rails is omakase",
    "tag": "rails",
    "authors": [
        {
            "id": "2",
            "name": "Steve"
        }
    ]
}

       你可以定义里的model关联关系如下:

//  app/serializers/post.js
export default DS.JSONSerialier.extend(DS.EmbeddedRecordsMixin, {
  attrs: {
author: {
  serialize: ‘records’,
  deserialize: ‘records’
}
  }
});

       如果你发生对象本身需要序列化与反序列化嵌入的关系,你可以使用属性embedded设置。

//  app/serializers/post.js
export default DS.JSONSerialier.extend(DS.EmbeddedRecordsMixin, {
  attrs: {
author: { embedded: ‘always’ }
  }
});

       序列化与反序列化设置有3个关键字:

  1. records 用于标记全部的记录都是序列化与反序列化的

  2. ids 用于标记仅仅序列化与反序列化记录的id

  3. false 用于标记记录不需要序列化与反序列化

 

例如,你可能会发现你想读一个嵌入式记录提取时一个JSON有效载荷只包括关系的身份在序列化记录。这可能是使用serialize: ids。你也可以选择通过设置序列化的关系 serialize: false

export default DS.JSONSerializer.extend(DS.EmbeddedRecordsMixin, {
  attrs: {
    author: {
      serialize: false,
      deserialize: 'records'
    },
    comments: {
      deserialize: 'records',
      serialize: 'ids'
    }
  }
});
11EMBEDDEDRECORDSMIXIN 默认设置

       如果你没有重写attrs去指定model的关联关系,那么EmbeddedRecordsMixin会有如下的默认行为:

belongsTo{serialize: ‘id’, deserialize: ‘id’ }

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

 

12,创作序列化器

       如果项目需要自定义序列化器,Ember推荐扩展JSONAIPSerializer或者JSONSerializer来实现你的需求。但是,如果你想完全创建一个全新的与JSONAIPSerializerJSONSerializer都不一样的序列化器你可以扩展DS.Serializer类,但是你必须要实现下面三个方法:

知道规范化JSON数据对Ember Data来说是非常重要的,如果model属性名不符合Ember Data规范这些属性值将不会自动更新。如果返回的数据没有在model中指定那么这些数据将会被忽略。比如下面的model定义,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/"
        }
      }
    }
}

       每个序列化记录必须按照这个格式要正确地转换成Ember Data记录。

 

更多有关于序列化器的介绍,请看下面的网址:

·       Ember Observer

·       GitHub

·       Bower

 

本篇的内容难度很大,并且也不好做演示例子,自己也理解不到位!!!希望能在后面深入学习Ember之后回来补充完整……

 

 

       到本篇为止,有关Ember的基础知识全部介绍完毕!!!从2015-08-26开始到刚好2个月,原计划是用3个月时间完成的,提前了一个月,归其原因是后面的内容难度大,理解偏差大!文章质量也不好,感觉时间比较仓促,说以节省了很多时间!

       介绍来打算介绍APPLICATION CONCERNSTESTING这两章!也有可能把旧版的Ember todomvc案例改成Ember2.0版本的,正好可以拿来练练手速!!!


 类似资料: