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

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

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

JSONAPISerializer规范

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

1,JSON 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数据看出,id为5的comment链接是"self": http://example.com/comments/5。id为12的comment链接是"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"
  }
}

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

//  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。

7,model之间的关联关系

一个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" },
      }
    }
  }
}

ID为1的comment关联了ID为5的post。

8,自定义转换规则

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

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

9,JSONSerializer

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

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

尽管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‘
    }
  }
});
11,EMBEDDEDRECORDSMIXIN 默认设置

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

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

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

12,创作序列化器

如果项目需要自定义序列化器,Ember推荐扩展JSONAIPSerializer或者JSONSerializer来实现你的需求。但是,如果你想完全创建一个全新的与JSONAIPSerializer、JSONSerializer都不一样的序列化器你可以扩展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 CONCERNS和TESTING这两章!也有可能把旧版的Ember todomvc案例改成Ember2.0版本的,正好可以拿来练练手速!!!

时间: 2024-10-15 06:23:53

Ember.js 入门指南——自定义序列号器的相关文章

Ember.js 入门指南——自定义包裹组件的HTML标签

按照惯例,先做好准备工作,使用Ember CLI命令生成演示所需的文件: ember g route customizing-component-element ember g component customizing-component-element ember g route home ember g route about 默认情况下,组件会被包裹在div标签内.比如,组件渲染之后得到下面的代码: <div id="ember180">   <h1>M

Ember.js 入门指南——自定义适配器

在Ember应用中适配器决定了数据保存到后台的方式,比如URL格式和请求头部.Ember Data默认的适配器是内置的REST API回调. 实际使用中经常会扩展默认的适配器.Ember的立场是应该通过扩展适配器来添加不同的功能,而非添加标识.这样可以使得代码更加容易测试.更加容易理解,同时也降低了可能需要扩展的适配器的代码. 如果你的后端使用的是Ember约定的规则那么可用使用适配器adapters/application.js.适配器application优先级比默认的适配器高,但是比指定的

Ember.js 入门指南——总目录

Ember.js 是什么?我想对于想学习它的人应该知道它是个什么东西,如果你想了解那就赶紧去 Google 或者百度,本系列教程是通过学习官网教程然后摘抄个人觉得比较重要的部分,加上学习实例整合而成,如有疏漏欢迎提出修改意见,一起成长! Ember官网:http://emberjs.com/ 教程官网:http://guides.emberjs.com/v2.0.0/ 在此简单介绍下 Ember: Ember是一个雄心勃勃的Web应用程序,消除了样板,并提供了一个标准的应用程序架构的JavaSc

Ember.js 入门指南——包裹内容

准备工作: ember g route wrapping-content-in-component-route        ember g component wrapping-content-in-component 有些情况下,你需要定义一个包裹其他模板提供的数据的组件.比如下面的例子: <!--  app/templates/components/wrapping-content-in-component.hbs  -->   <h1>{{title}}</h1>

Ember.js 入门指南——异步路由

本文将为你介绍路由的高级特性,这些高级特性可以用于处理项目复杂的异步逻辑. 关于单词promises,直译是承诺,但是个人觉得还是使用原文吧.读起来顺畅点. 1,promises(承诺) Ember的路由处理异步逻辑的方式是使用promises.简而言之,promises就是一个表示最终结果的对象.这个对象可能是fulfill(成功获取最终结果)也可能是reject(获取结果失败).为了获取这个最终值,或者是处理promises失败的情况都可以使用then方法,这个方法接受两个可选的回调方法,一

Ember.js 入门指南--目录

本系列文章全部从(http://ibeginner.sinaapp.com/)迁移过来,欢迎访问原网站. Ember.js 是什么?我想对于想学习它的人应该知道它是个什么东西,如果你想了解那就赶紧去 Google 或者百度,本系列教程是通过学习官网教程然后摘抄个人觉得比较重要的部分,加上学习实例整合而成,如有疏漏欢迎提出修改意见,一起成长! Ember官网:http://emberjs.com/ 教程官网:http://guides.emberjs.com/v2.0.0/ 在此简单介绍下 Emb

Ember.js 入门指南——路由定义

当你的应用启动的时候,路由器就会匹配当前的URL到你定义的路由上.然后按照定义的路由层次逐个加载数据.设置应用程序状态.渲染路由对应的模板. 1,基本路由 在app/router.js的map方法里定义的路由会映射到当前的URL.当map方法被调用的时候方法体内的route方法就会创建路由. 下面使用Ember CLI命令创建两个路由: ember generate route about ember generate route favorites 命令执行完之后你可在你的项目目录app/ro

Ember.js 入门指南——模板渲染

路由的另一个重要职责是渲染同名字的模板. 比如下面的路由设置,posts路由渲染模板posts.hbs,路由new渲染模板posts/new.hbs. Router.map(function() {      this.route('posts', function() {      this.route('new');   }); }); 每一个模板都会渲染到父模板的{{outlet}}上.比如上面的路由设置模板posts.hbs会渲染到模板application.hbs的{{outlet}}

Ember.js 入门指南——属性传递

1,传递参数到组件上 每个组件都是相对独立的,因此任何组件所需的数据都需要通过组件的属性把数据传递到组件中. 比如上篇<Ember.js 入门指南--组件定义>的第三点"{{component item.pn post=item}}"就是通过属性post把数据传递到组件foo-component或者bar-component上.如果在index.hbs中是如下方式调用组件那么渲染之后的页面是空的. {{component item.pn}} 请读者自己修改index.hbs