So you're working with a web service that doesn't play nice with Ember Data, that's okay! Using Ember Data 1.0.0-beta we will normalize ugly JSON feeds into something that Ember understands and loves.
11. For example
{!
"posts": [!
{!
"id": 5,!
"title":You won't believe what was hiding in this kid's locker",!
"body": "...",!
"author": {!
"name": "Jeremy Gillick",!
"role": "Author",!
"email": "spam-me@please.com"!
}!
}!
]!
}
12. {!
"posts": [!
{!
"id": 6,!
"title": "New Study: Apricots May Help Cure Glaucoma",!
"body": "...",!
"author": {!
"name": "Jeremy Gillick",!
"role": "Author",!
"email": "spam-me@please.com"!
}!
},!
{!
"id": 5,!
"title": "You won't believe what was hiding in this kid's locker",!
"body": "...",!
"author": {!
"name": "Jeremy Gillick",!
"role": "Author",!
"email": "spam-me@please.com"!
}!
}!
]!
}
For example
Redundant, adds feed bloat and
which one is the source of truth?
13. This is better
{!
"posts": [!
{!
"id": 4,!
"title": "New Study: Apricots May Help Cure Glaucoma",!
"body": "...",!
"author": 42!
},!
{!
"id": 5,!
"title": "You won't believe what was hiding in this kid's locker",!
"body": "...",!
"author": 42!
}!
],!
"users": [!
{!
"id": 42,!
"name": "Jeremy Gillick",!
"role": "Author",!
"email": "spam-me@please.com"!
}!
]!
}
14. Ember Data Expects
{!
"modelOneRecord": {!
...!
}!
"modelTwoRecords": [!
{ ... },!
{ ... }!
],!
"modelThreeRecords": [!
{ ... },!
{ ... }!
]!
}
No further nesting is allowed
20. How do we do this?
With a custom Ember Data Serializer!
21. Two common ways
• Create ProductSerializer that manually converts the
JSON
• A lot of very specific code that you’ll have to repeat for all nested
JSON payloads.
• Build a generic serializer that automatically flattens
nested JSON objects
• Good, generic, DRY
23. Steps
• Loop through all root JSON properties
• Determine which model they represent
• Get all the relationships for that model
• Side load any of those relationships
26. Create a Serializer
/**!
Deserialize a nested JSON payload into a flat object!
with sideloaded relationships that Ember Data can import.!
*/!
App.NestedSerializer = DS.RESTSerializer.extend({!
!
/**!
(overloaded method)!
Deserialize a JSON payload from the server.!
!
@method normalizePayload!
@param {Object} payload!
@return {Object} the normalized payload!
*/!
extract: function(store, type, payload, id, requestType) {!
return this._super(store, type, payload, id, requestType);!
}!
!
});
31. extract: function(store, type, payload, id, requestType) {!
var rootKeys = Ember.keys(payload);!
!
// Loop through root properties and process their relationships!
rootKeys.forEach(function(key){!
var type = store.container!
! ! ! ! .lookupFactory('model:' + key.singularize());!
!
}, this);!
!
return this._super(store, type, payload, id, requestType);!
}
{!
"products": [!
{!
...!
}!
]!
}
32. {!
"products": !
[!
{!
...!
}!
]!
}
extract: function(store, type, payload, id, requestType) {!
var rootKeys = Ember.keys(payload);!
!
// Loop through root properties and process their relationships!
rootKeys.forEach(function(key){!
var type = store.container!
.lookupFactory('model:' + key.singularize()),!
hash = payload[key];!
!
}, this);!
!
return this._super(store, type, payload, id, requestType);!
}
33. extract: function(store, type, payload, id, requestType) {!
var rootKeys = Ember.keys(payload);!
!
// Loop through root properties and process their relationships!
rootKeys.forEach(function(key){!
var type = store.container!
.lookupFactory('model:' + key.singularize()),!
hash = payload[key];!
!
// Sideload embedded relationships of this model hash!
if (type) {!
this.processRelationships(store, type, payload, hash);!
}!
}, this);!
!
return this._super(store, type, payload, id, requestType);!
}
{!
"products": !
[!
{!
...!
}!
]!
}
34. /**!
Process nested relationships on a single hash record!
!
@method extractRelationships!
@param {DS.Store} store!
@param {DS.Model} type!
@param {Object} payload The entire payload!
@param {Object} hash The hash for the record being processed!
@return {Object} The updated hash object!
*/!
processRelationships: function(store, type, payload, hash) {!
!
},
35. {!
"products": [!
{!
...!
}!
]!
}
processRelationships: function(store, type, payload, hash) {!
!
// If hash is an array, process each item in the array!
if (hash instanceof Array) {!
hash.forEach(function(item, i){!
hash[i] = this.processRelationships(store, type, payload, item);!
}, this);!
}!
!
return hash;!
},
36. {!
"products": [!
{!
...!
}!
]!
}
processRelationships: function(store, type, payload, hash) {!
!
// If hash is an array, process each item in the array!
if (hash instanceof Array) {!
hash.forEach(function(item, i){!
hash[i] = this.processRelationships(store, type, payload, item);!
}, this);!
}!
!
else {!
!
}!
!
return hash;!
},
37. {!
"products": [!
{!
...!
}!
]!
}
processRelationships: function(store, type, payload, hash) {!
!
// If hash is an array, process each item in the array!
if (hash instanceof Array) {!
hash.forEach(function(item, i){!
hash[i] = this.processRelationships(store, type, payload, item);!
}, this);!
}!
!
else {!
!
// Find all relationships in this model!
type.eachRelationship(function(key, relationship) {!
!
}, this);!
}!
!
return hash;!
},
39. {!
"products": [!
{!
"name": "Robot",!
"description": "...",!
"price": !
{!
"value": 59.99,!
"currency": "USD"!
},!
"size": {!
"height": 24,!
"width": 12,!
"depth": 14!
},!
"options": [!
{!
"name": "Color",!
"values": ["silver", !
"black", !
"#E1563F"]!
}!
]!
}!
]!
}
processRelationships: function(store, type, payload, hash) {!
!
// If hash is an array, process each item in the array!
if (hash instanceof Array) {!
hash.forEach(function(item, i){!
hash[i] = this.processRelationships(store, type, payload, item);!
}, this);!
}!
!
else {!
!
// Find all relationships in this model!
type.eachRelationship(function(key, relationship) {!
var related = hash[key]; // The hash for this relationship!
!
}, this);!
}!
!
return hash;!
},
40. processRelationships: function(store, type, payload, hash) {!
!
// If hash is an array, process each item in the array!
if (hash instanceof Array) {!
hash.forEach(function(item, i){!
hash[i] = this.processRelationships(store, type, payload, item);!
}, this);!
}!
!
else {!
!
// Find all relationships in this model!
type.eachRelationship(function(key, relationship) {!
var related = hash[key], // The hash for this relationship!
relType = relationship.type; // The model for this relationship
!
}, this);!
}!
!
return hash;!
},
{!
"products": [!
{!
"name": "Robot",!
"description": "...",!
"price": !
{!
"value": 59.99,!
"currency": "USD"!
},!
"size": {!
"height": 24,!
"width": 12,!
"depth": 14!
},!
"options": [!
{!
"name": "Color",!
"values": ["silver", !
"black", !
"#E1563F"]!
}!
]!
}!
]!
}
App.Price
41. processRelationships: function(store, type, payload, hash) {!
!
// If hash is an array, process each item in the array!
if (hash instanceof Array) {!
hash.forEach(function(item, i){!
hash[i] = this.processRelationships(store, type, payload, item);!
}, this);!
}!
!
else {!
!
// Find all relationships in this model!
type.eachRelationship(function(key, relationship) {!
var related = hash[key], !
relType = relationship.type;!
!
hash[key] = this.sideloadRecord(store, relType, payload, related);!
!
}, this);!
}!
!
return hash;!
},
{!
"products": [!
{!
"name": "Robot",!
"description": "...",!
"price": !
{!
"value": 59.99,!
"currency": "USD"!
},!
"size": {!
"height": 24,!
"width": 12,!
"depth": 14!
},!
"options": [!
{!
"name": "Color",!
"values": ["silver", !
"black", !
"#E1563F"]!
}!
]!
}!
]!
}
42. /**!
Sideload a record hash to the payload!
!
@method sideloadRecord!
@param {DS.Store} store!
@param {DS.Model} type!
@param {Object} payload The entire payload!
@param {Object} hash The record hash object!
@return {Object} The ID of the record(s) sideloaded!
*/!
sideloadRecord: function(store, type, payload, hash) {!
!
},
43. sideloadRecord: function(store, type, payload, hash) {!
var id, sideLoadkey, sideloadArr, serializer;!
!
// If hash is an array, sideload each item in the array!
if (hash instanceof Array) {!
id = [];!
hash.forEach(function(item, i){!
id[i] = this.sideloadRecord(store, type, payload, item);!
}, this);!
}!
!
return id;!
},
{!
"products": [!
{!
"name": "Robot",!
"description": "...",!
"price": !
{!
"value": 59.99,!
"currency": "USD"!
},!
"size": {!
"height": 24,!
"width": 12,!
"depth": 14!
},!
"options": [!
{!
"name": "Color",!
"values": ["silver", !
"black", !
"#E1563F"]!
}!
]!
}!
]!
}
44. sideloadRecord: function(store, type, payload, hash) {!
var id, sideLoadkey, sideloadArr, serializer;!
!
// If hash is an array, sideload each item in the array!
if (hash instanceof Array) {!
id = [];!
hash.forEach(function(item, i){!
id[i] = this.sideloadRecord(store, type, payload, item);!
}, this);!
}!
// Sideload record!
else if (typeof hash === 'object') {!
!
}!
return id;!
},
{!
"products": [!
{!
"name": "Robot",!
"description": "...",!
"price": !
{!
"value": 59.99,!
"currency": "USD"!
},!
"size": {!
"height": 24,!
"width": 12,!
"depth": 14!
},!
"options": [!
{!
"name": "Color",!
"values": ["silver", !
"black", !
"#E1563F"]!
}!
]!
}!
]!
}