-
-
Save hussfelt/064506796c7e94e85e47 to your computer and use it in GitHub Desktop.
WRONG IMPLEMENTATION! RestSerializer Mixin to convert old API data to new JSONApi 2.0
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import DS from 'ember-data'; | |
import rest_serializer from 'app/mixins/serializers/rest'; | |
export default DS.RESTSerializer.extend(rest_serializer, { | |
/** | |
* normalizeResponseExtra, triggered from rest_serializer | |
* @param {[type]} data [description] | |
* @param {[type]} payload [description] | |
* @param {[type]} primaryModelClass [description] | |
* @return {[type]} [description] | |
*/ | |
normalizeResponseExtra: function(data, payload, primaryModelClass) { | |
// If we have sideloaded_data in the original payload | |
if (typeof payload.sideloaded_data !== 'undefined') { | |
// This is where you could work with and convert any sideloaded data | |
// And then build on the "data" variable | |
} | |
// Return the data | |
return data; | |
}, | |
/** | |
* normalizeArrayResponseExtra, triggered from rest_serializer | |
* @param {[type]} data [description] | |
* @param {[type]} payload [description] | |
* @param {[type]} primaryModelClass [description] | |
* @return {[type]} [description] | |
*/ | |
normalizeArrayResponseExtra: function(data, payload/*, primaryModelClass*/) { | |
// Normalize sideloaded-singular-type-name | |
data = this.normalizeSideloadedData(data, payload, 'sideloaded-singular-type-name', 'models-related-property-name'); | |
// Return JSONApi 2.0 document | |
return data; | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Ember from 'ember'; | |
//import DS from 'ember-data'; | |
export default Ember.Mixin.create({ | |
isNewSerializerAPI: true, | |
/** | |
* [normalizeSingleResponse description] | |
* @param {[type]} store [description] | |
* @param {[type]} primaryModelClass [description] | |
* @param {[type]} payload [description] | |
* @param {[type]} id [description] | |
* @param {[type]} requestType [description] | |
* @return {[type]} [description] | |
*/ | |
normalizeSingleResponse: function(store, primaryModelClass, payload/*, id, requestType*/) { | |
// Check if we want to do more normalization-before | |
if (typeof this.normalizeSingleResponseExtraBefore === 'function') { | |
// Overwrite data | |
payload = this.normalizeSingleResponseExtraBefore(payload, primaryModelClass); | |
} | |
var data = {}, | |
extracted = {}, | |
root = 'data', | |
type = primaryModelClass.modelName; | |
// set the ID if there is one | |
if (typeof payload[type]['id'] !== 'undefined') { | |
extracted.id = payload[type]['id']; | |
} | |
// Set attributes | |
extracted.attributes = payload[type]; | |
delete extracted.attributes.id; | |
// set the type | |
extracted.type = type; | |
// Set data | |
data[root] = extracted; | |
// Normalize any relations for this object | |
data = this.normalizeSingleResponseRelations(data); | |
// Check if we want to do more normalization | |
if (typeof this.normalizeSingleResponseExtra === 'function') { | |
// Overwrite data | |
data = this.normalizeSingleResponseExtra(data, payload, primaryModelClass); | |
} | |
// Return the JSONApi 2.0 payload | |
return data; | |
}, | |
/** | |
* normalizeArrayResponse description | |
* @param {[type]} store [description] | |
* @param {[type]} primaryModelClass [description] | |
* @param {[type]} payload [description] | |
* @param {[type]} id [description] | |
* @param {[type]} requestType [description] | |
* @return {[type]} [description] | |
*/ | |
normalizeArrayResponse: function(store, primaryModelClass, payload/*, id, requestType*/) { | |
var data = {}, | |
extracted = [], | |
root = 'data', | |
type = Ember.String.underscore(Ember.String.pluralize(primaryModelClass.modelName)), | |
typeSingular = Ember.String.underscore(primaryModelClass.modelName); | |
// Check if we want to do more normalization-before | |
if (typeof this.normalizeArrayResponseExtraBefore === 'function') { | |
// Overwrite payload | |
payload = this.normalizeArrayResponseExtraBefore(payload, primaryModelClass); | |
} | |
//Check if payload type exists | |
if (typeof payload[type] === 'undefined') { | |
throw new Error('Missing payload data: ' + type); | |
} | |
payload[type].forEach(function(e) { | |
e.attributes = {}; | |
// iterate through the object and push any attributes into the attributes array | |
Object.keys(e).forEach(function(key) { | |
// but don't push id or attributes itself into the array | |
if (key !== 'id' && key !== 'attributes') { | |
e.attributes[key] = e[key]; | |
delete e[key]; | |
} | |
}); | |
// set the type | |
e.type = typeSingular; | |
// set the ID if there is one | |
if (typeof e['id'] !== 'undefined') { | |
e.id = e['id']; | |
} | |
extracted.push(e); | |
}); | |
data[root] = extracted; | |
// Check if we want to do more normalization | |
if (typeof this.normalizeArrayResponseExtra === 'function') { | |
// Overwrite data | |
data = this.normalizeArrayResponseExtra(data, payload, primaryModelClass); | |
} | |
// Last but not least, check if we have anything we want to move to relational object | |
// We do this after everything because sometimes we trigger sideloaded data, and that | |
// itself actually extracts what it needs into a relation-object allready. | |
// This trigger only takes all properties left in the attributes object and converts | |
// what it needs to the relations object | |
data = this.normalizeArrayResponseRelations(data); | |
// Return the JSONApi 2.0 payload | |
return data; | |
}, | |
/** | |
* [normalizeSideloadedData description] | |
* @param {[type]} data [description] | |
* @param {[type]} payload [description] | |
* @param {[type]} modelNameToLookFor [description] | |
* @param {[type]} modelRelationProperty [description] | |
* @return {[type]} [description] | |
*/ | |
normalizeSideloadedData: function(data, payload, modelNameToLookFor, modelRelationProperty) { | |
// Pluralize type | |
var type = Ember.String.underscore(Ember.String.pluralize(modelNameToLookFor)); | |
// Check if it has sideloaded type data | |
if (typeof payload[type] !== 'undefined') { | |
var extracted = [], | |
typeSingular = Ember.String.underscore(modelNameToLookFor); | |
payload[type].forEach(function(e) { | |
e.attributes = {}; | |
// iterate through the object and push any attributes into the attributes array | |
Object.keys(e).forEach(function(key) { | |
// but don't push id or attributes itself into the array | |
if (key !== 'id' && key !== 'attributes') { | |
e.attributes[key] = e[key]; | |
delete e[key]; | |
} | |
}); | |
// set the type | |
e.type = typeSingular; | |
// Break application if broken sideloaded data | |
if (typeof e['id'] === 'undefined') { | |
throw new Error('Invalid sideloaded data:' + type); | |
} | |
// Set id | |
e.id = e['id']; | |
// Push the object | |
extracted.push(e); | |
}); | |
// Check if we already have an included property | |
if (typeof data.included === 'undefined') { | |
data.included = []; | |
} | |
// Concatenate array data with the included property | |
data.included = data.included.concat(extracted); | |
// Loop through and remove all the relationship data in the original entities, convert to relationships | |
data.data.forEach(function(o) { | |
// Check if we have a relationships container | |
if (typeof o.relationships === 'undefined') { | |
o.relationships = {}; | |
} | |
// Check if we have a contaner for this modelRelationProperty | |
if (typeof o.relationships[modelRelationProperty] === 'undefined') { | |
o.relationships[modelRelationProperty] = {}; | |
o.relationships[modelRelationProperty].data = []; | |
} | |
// Convert the attributes to relationship | |
o.attributes[modelRelationProperty].forEach(function(relation) { | |
o.relationships[modelRelationProperty].data.push({ | |
id: relation, | |
type: typeSingular | |
}); | |
}); | |
// Delete the attribute-data | |
delete o.attributes[modelRelationProperty]; | |
}); | |
} | |
// Return the modified JSONApi 2.0 data payload | |
return data; | |
}, | |
/** | |
* [normalizeArrayResponseRelations description] | |
* @param {[type]} data [description] | |
* @return {[type]} [description] | |
*/ | |
normalizeArrayResponseRelations: function (data) { | |
// Loop through all records | |
for (var y=0;y<data.data.length;y++) { | |
// Get all properties for this model | |
var propertiesArray = this.mapPropertiesToModel(data.data[y].type); | |
// Do nothing if there are no relations for this model | |
if (propertiesArray.length === 0) { | |
return data; | |
} | |
// Check if we have a relationships container | |
if (typeof data.data[y].relationships === 'undefined') { | |
data.data[y].relationships = {}; | |
} | |
// Loop through the relationship properties | |
for (var i=0; i<propertiesArray.length;i++) { | |
// Make sure we have the property in the payload | |
if (typeof data.data[y].attributes[propertiesArray[i].property] === 'undefined') { | |
continue; | |
} | |
// Check if we have a contaner for this modelRelationProperty | |
if (typeof data.data[y].relationships[propertiesArray[i].property] === 'undefined') { | |
data.data[y].relationships[propertiesArray[i].property] = {}; | |
if (propertiesArray[i].array) { | |
data.data[y].relationships[propertiesArray[i].property].data = []; | |
} else { | |
data.data[y].relationships[propertiesArray[i].property].data = {}; | |
} | |
} | |
// Convert the attributes to relationship | |
if (propertiesArray[i].array) { | |
// Loop through the relation | |
for(var x=0;x<data.data[y].attributes[propertiesArray[i].property].length;x++) { | |
// Add a relationship for each record | |
data.data[y].relationships[propertiesArray[i].property].data.push({ | |
id: data.data[y].attributes[propertiesArray[i].property][x], | |
type: propertiesArray[i].model | |
}); | |
} | |
} else { | |
data.data[y].relationships[propertiesArray[i].property].data = { | |
id: data.data[y].attributes[propertiesArray[i].property], | |
type: propertiesArray[i].model | |
}; | |
} | |
// Delete the attribute-data | |
delete data.data[y].attributes[propertiesArray[i].property]; | |
} | |
} | |
// Return the prepared object with relational data | |
return data; | |
}, | |
/** | |
* [normalizeSingleResponseRelations description] | |
* @param {[type]} data [description] | |
* @return {[type]} [description] | |
*/ | |
normalizeSingleResponseRelations: function (data) { | |
// Get all properties for this model | |
var propertiesArray = this.mapPropertiesToModel(data.data.type); | |
// Do nothing if there are no relations for this model | |
if (propertiesArray.length === 0) { | |
return data; | |
} | |
// Check if we have a relationships container | |
if (typeof data.data.relationships === 'undefined') { | |
data.data.relationships = {}; | |
} | |
// Loop through the relationship properties | |
for (var i=0; i<propertiesArray.length;i++) { | |
// Make sure we have the property in the payload | |
if (typeof data.data.attributes[propertiesArray[i].property] === 'undefined') { | |
continue; | |
} | |
// Check if we have a contaner for this modelRelationProperty | |
if (typeof data.data.relationships[propertiesArray[i].property] === 'undefined') { | |
data.data.relationships[propertiesArray[i].property] = {}; | |
if (propertiesArray[i].array) { | |
data.data.relationships[propertiesArray[i].property].data = []; | |
} else { | |
data.data.relationships[propertiesArray[i].property].data = {}; | |
} | |
} | |
// Convert the attributes to relationship | |
if (propertiesArray[i].array) { | |
// Loop through the relation | |
for(var x=0;x<data.data.attributes[propertiesArray[i].property].length;x++) { | |
// Add a relationship for each record | |
data.data.relationships[propertiesArray[i].property].data.push({ | |
id: data.data.attributes[propertiesArray[i].property][x], | |
type: propertiesArray[i].model | |
}); | |
} | |
} else { | |
data.data.relationships[propertiesArray[i].property].data = { | |
id: data.data.attributes[propertiesArray[i].property], | |
type: propertiesArray[i].model | |
}; | |
} | |
// Delete the attribute-data | |
delete data.data.attributes[propertiesArray[i].property]; | |
} | |
// Return the prepared object with relational data | |
return data; | |
}, | |
/** | |
* mapPropertiesToModel | |
* Will return model type for a property | |
* @param {[type]} model [description] | |
* @return {[type]} [description] | |
*/ | |
mapPropertiesToModel: function(model) { | |
/** | |
* ****************************************** | |
* NOTE: | |
* This is the unfortunate part, where we need | |
* to re-map all the model-relations. | |
* We do this and we describe the property they | |
* are extracted from, which model they are from | |
* and if it's a belongsTo/hasMany (array:true/false) | |
* | |
* I am looking for a better way to fix this | |
* by letting ember tell me what the relation is... | |
* Any feedback would be greatly appriciated! | |
* ****************************************** | |
*/ | |
// Create map | |
var map = { | |
category: [ | |
{ | |
property: 'items', // This is the item property in my category model | |
model: 'item', // It belongs to the item model | |
array: true // And they are "many" | |
} | |
] | |
}; | |
// Check that it exists, else return empty array | |
if (typeof map[model] === 'undefined') { | |
return []; | |
} | |
// Return | |
return map[model]; | |
} |
Don't use this implementation. It's wrong! No need to do all this :)
Correct implementation here:
https://gist.github.com/hussfelt/9c9002f15cc253278edb
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Blogpost here: http://hussfelt.net/2015/08/10/understanding-and-converting-new-jsonapi-2-0/