在Ember应用中,序列化器会格式化与后台交互的数据,包括发送和接收的数据。默认情况下会使用JSON API序列化数据。如果你的后端使用不同的格式,Ember Data允许你自定义序列化器或者定义一个完全不同的序列化器。
Ember Data内置了三个序列化器。JSONAPISerializer是默认的序列化器,用与处理后端的JSON API。JSONSerializer是一个简单的序列化器,用与处理单个JSON对象或者是处理记录数组。RESTSerializer是一个复杂的序列化器,支持侧面加载,在Ember Data2.0之前是默认的序列化器。
当你向服务器请求数据时,JSONSerializer会把服务器返回的数据当做是符合下列规范的JSON数据。
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" } } ] }
数据有时候并不是请求的主体,如果数据有链接。链接的关系会放在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内。
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); } });
有关更多自定义序列化器请移步官网文档。
每一条数据都有一个唯一值作为ID,默认情况下Ember会为每个model加上一个名为id的属性。如果你想改为其他名称,你可以在序列化器中指定。
// app/serializers/application.js import DS from 'ember-data'; export default DS.JSONSerializer.extend({ primatyKey: '__id' });
把数据主键名修改为“__id”。
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); } });
如果你想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。
一个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。
在某些情况下,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({ // ... });
尽管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个关键字:
records 用于标记全部的记录都是序列化与反序列化的
ids 用于标记仅仅序列化与反序列化记录的id
false 用于标记记录不需要序列化与反序列化
例如,你可能会发现你想读一个嵌入式记录提取时一个JSON有效载荷只包括关系的身份在序列化记录。这可能是使用serialize: ids。你也可以选择通过设置序列化的关系 serialize: false。
export default DS.JSONSerializer.extend(DS.EmbeddedRecordsMixin, { attrs: { author: { serialize: false, deserialize: 'records' }, comments: { deserialize: 'records', serialize: 'ids' } } });
如果你没有重写attrs去指定model的关联关系,那么EmbeddedRecordsMixin会有如下的默认行为:
belongsTo:{serialize: ‘id’, deserialize: ‘id’ }
hasMany: { serialize: false, deserialize: ‘ids’ }
如果项目需要自定义序列化器,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记录。
更多有关于序列化器的介绍,请看下面的网址:
· GitHub
· Bower
本篇的内容难度很大,并且也不好做演示例子,自己也理解不到位!!!希望能在后面深入学习Ember之后回来补充完整……
到本篇为止,有关Ember的基础知识全部介绍完毕!!!从2015-08-26开始到现在刚好2个月,原计划是用3个月时间完成的,提前了一个月,归其原因是后面的内容难度大,理解偏差大!文章质量也不好,感觉时间比较仓促,说以节省了很多时间!
介绍来打算介绍APPLICATION CONCERNS和TESTING这两章!也有可能把旧版的Ember todomvc案例改成Ember2.0版本的,正好可以拿来练练手速!!!