To this point we have glossed over both Serializers and Adapters. We briefly looked at an adapter as we customized the host property and looked at changing headers and namespace properties. We haven't really looked at serializers at all and we have gotten away with that to this point because where we have used a backend we have used one that serves up a JSON API compliant payload. But, what if we had a non-compliant payload. Well, I have set up a basic rails 5 api only app here There is a link to the repo in the notes. This app serves up a payload that looks like this:

[
  {
    "id": 4,
    "title": "Community",
    "description": "Community is an American television sitcom created by Dan Harmon that premiered on NBC on September 17, 2009. The single-camera series follows an ensemble cast of characters played by Joel McHale, Gillian Jacobs, Danny Pudi, Yvette Nicole Brown, Alison Brie, Donald Glover, Ken Jeong, Chevy Chase, and Jim Rash at a community college in the fictional town of Greendale, Colorado. It makes heavy use of meta-humor and pop culture references, often parodying film and television clichés and tropes.",
    "created_at": "2016-09-20T00:55:43.437Z",
    "updated_at": "2016-09-20T00:55:43.437Z"
  },
  {
    "id": 5,
    "title": "Chuck",
    "description": "Chuck is an American action-comedy/spy-drama television series created by Josh Schwartz and Chris Fedak. The series is about an average computer-whiz-next-door named Chuck, played by Zachary Levi, who receives an encoded e-mail from an old college friend now working for the Central Intelligence Agency (CIA).",
    "created_at": "2016-09-20T00:55:43.443Z",
    "updated_at": "2016-09-20T00:55:43.443Z"
  },
  {
    "id": 6,
    "title": "Star Trek: The Next Generation",
    "description": "Star Trek: The Next Generation (abbreviated as TNG and ST:TNG) is an American science fiction television series created by Gene Roddenberry that ran between 1987 and 1994. Roddenberry, Maurice Hurley, Rick Berman, and Michael Piller served as executive producers at different times throughout its production.",
    "created_at": "2016-09-20T00:55:43.446Z",
    "updated_at": "2016-09-20T00:55:43.446Z"
  },
  {
    "id": 7,
    "title": "The X-Files",
    "description": "The X-Files is an American science fiction horror drama television series created by Chris Carter. The program originally aired from September 10, 1993, to May 19, 2002, on Fox, spanning nine seasons, with 202 episodes and a feature film of the same name, before returning with a second film in 2008 and a six-episode tenth season in 2016. The series revolves around FBI special agents Fox Mulder (David Duchovny) and Dana Scully (Gillian Anderson) who investigate X-Files: marginalized, unsolved cases involving paranormal phenomena.",
    "created_at": "2016-09-20T00:55:43.448Z",
    "updated_at": "2016-09-20T00:55:43.448Z"
  }
]

As you can see, this is not a JSON API compliant payload. It is just some JSON - an array of objects. So, the question is how do we get Ember to consume this payload and produce models that we can use. To follow along, make sure your rails server is up and running. First, let's bootstrap an Ember app.

ember new show_frontend
cd show_frontend
ember g model show title:string description:string # perhaps not the best model name nonetheless...
ember g route index
vim app/routes/index.js

app/routes/index.js:

import Ember from 'ember';

export default Ember.Route.extend({
  model(){
    return this.store.findAll('show');
  }
});

app/templates/index.js:

<ul>
  {{#each model as |show|}}
    <li><strong>{{show.title}}</strong> - {{show.description}}</li>
  {{/each}}
</ul>

Now, let's create an adapter to tell our ember app where to get its data from. Then let's open up our new adapter and add a host.

ember g adapter application
vim app/adapters/application.js
import DS from 'ember-data';

export default DS.JSONAPIAdapter.extend({
  host: 'http://localhost:3000'
});

Now, if we start our Ember server and navigate to http://localhost:4200 we can see in our console some helpful errors that basically just say that the JSON document that we received from our server is not formatted like we expect. The error suggests that we use the serializer's normalizeResponse hook to munge the server response into a JSONAPI compliant document. But, in our case we don't have to do that. Let's start by generating a serializer:

ember g serializer application

Then let's open it up:

vim app/serializers/application.js

In our case, all we have to do to make this work is change export default DS.JSONAPISerializer.extend to export default DS.JSONSerializer.extend. The JSONSerializer does not expect JSON API document, but a very basic JSON document like that being provided by Rails in our example. If your use case is more complicated, you can use the serializer's normalizeResponse hook to make the document look like we want. Let's change our serializer back to the JSONAPISerializer and use normalizeResponse to turn our data into a JSONAPI compliant payload.

import DS from 'ember-data';

export default DS.JSONAPISerializer.extend({
  normalizeResponse(store, primaryModelClass, payload, id, requestType){
    let mungedPayload = {};
    mungedPayload.data = payload.map(item => {
      let currentItem = { id: item.id, type: primaryModelClass.modelName, attributes: item };
      delete currentItem.attributes.id;
      return currentItem;
    });
    return this._super(store, primaryModelClass, mungedPayload, id, requestType);
  }
});

So, this is a really naive approach, it obviously works for a findAll request, but it would be less effective with a findRecord request. Fortunately, Ember Data gives us access to the request type by way of the requestType param. We could use this param to property format our response depending on the type of request, or we could use one of the type specific hooks from the serializer.

DS.JSONAPISerializer Methods

These functions are called by the serializer depending on the requestType. So, for example, if we wanted to just override for a findAll request we could use the normalizeFindAllResponse method or we could override the normalizeArrayResponse which is called by all of the normalize functions that return an array like value. So what function you decide to override really depends on your specific case. Regardless of what function you override, in most cases it will probably be valuable to call this._super and just normalize the piece of the data that needs work rather than trying to serialize everything your self, which will probably end up brittle and be hard to upgrade, maintain, and debug.

That is it for today.

Links: