How to serialize?


#1

hey guys, I’m new in EmberJS world, and I don’t understand the concept of this serialize stuff.

My question is I have the JSON response from REST API like this, how can I serialize to format that Ember can understand?


#2

Hi @yuanda, welcome to Ember! Serializing can get pretty complex but hopefully you won’t have to do much customization, so I’ll try and explain the basics and give you a starting point.

First I want to note that technically this is only required if you want to use Ember Data, which is not by any means required to use Ember. That said it does a lot for you and I’m assuming you want to if you’re asking about it, so let’s dive in.

Ember Data uses adapters (for formatting/making requests to your backend) and serializers (for formatting data to/from your backend). Often you can get away with defining a single application-wide adapter/serializer pair, but technically speaking each model type can have its own adapter/serializer. Ember Data ships with three types of adapters/serializers: JSONAdapter/JSONSerializer (the simplest), RESTAdapter/RESTSerializer (a little more complex), and JSONAPIAdapter/JSONAPISerializer (the most complex/robust). If your backend likes its JSON in any of these three default formats you can just plug and play, but if your backend is slightly different (often the case) you’ll need to do a little customization. Ember “prefers” JSONAPI formatted data so the goal of your serializer is to transform data from whatever format your backend emits/expects into JSONAPI format. To do this you want to pick whichever of the three default serializers matches your backend most closely, and then override one or more of the serialization methods to munge your data properly.

JSONSerializer expects payloads that look like this (from the docs):

{
  id: 1,
  name: 'Sebastian',
  friends: [3, 4],
  links: {
    house: '/houses/lefkada'
  }
}

One of the key differences between this and the RESTSerializer payloads is that the RESTSerializer usually expects a model type as part of the payload. To me it looks like your payload matches JSONSerializer most closely, so I’d start there. The main difference between your payload and what JSONSerializer expects is that your array is wrapped in an object with a “data” key, so simply unwrapping that in the normalizeArrayResponse method should be sufficient, something like:

// serializers/application.js

import DS from 'ember-data';

export default DS.JSONSerializer.extend({
  normalizeArrayResponse(store, primaryModelClass, payload, id, requestType) {
    let newPayload = payload.data;
    return this._super(store, primaryModelClass, newPayload, id, requestType);
  }
});


#3

I should also mention there are a LOT of methods that can be customized on a serializer, so it can help to categorize them a little bit:

Serializer methods vs normalize methods: serialize methods convert payloads from Ember to the format your backend expects (POST, PUT, PATCH, etc), normalize methods do the opposite, convert payloads from your backend format to what Ember wants (JSON API format).

Then, when normalizing for example, there’s a hierarchy to how the methods are used to normalize a payload:

  1. normalizeResponse is called for (at least I think) every response. This basically just looks at what the request was and delegates the normalization to a more specific methods, which are specialized for each type of Ember Data operation:

  2. specialized methods (this is literally just a copy paste from the actual code):

  normalizeResponse(store, primaryModelClass, payload, id, requestType) {
    switch (requestType) {
      case 'findRecord':
        return this.normalizeFindRecordResponse(...arguments);
      case 'queryRecord':
        return this.normalizeQueryRecordResponse(...arguments);
      case 'findAll':
        return this.normalizeFindAllResponse(...arguments);
      case 'findBelongsTo':
        return this.normalizeFindBelongsToResponse(...arguments);
      case 'findHasMany':
        return this.normalizeFindHasManyResponse(...arguments);
      case 'findMany':
        return this.normalizeFindManyResponse(...arguments);
      case 'query':
        return this.normalizeQueryResponse(...arguments);
      case 'createRecord':
        return this.normalizeCreateRecordResponse(...arguments);
      case 'deleteRecord':
        return this.normalizeDeleteRecordResponse(...arguments);
      case 'updateRecord':
        return this.normalizeUpdateRecordResponse(...arguments);
    }
  },
  1. These methods, in turn, mostly delegate to a couple more abstract methods: normalizeSingleResponse (for things like findRecord), normalizeSaveResponse (for things like create, update), or normalizeArrayResponse.

  2. Eventually all records should end up in the “normalize” method, which is responsible for transforming a single record payload into a single JSON API payload for inclusion into the store.

So in the example you posted above (let’s assume it was from store.findAll though it could have been from store.query) the methods invoked for normalization would be something like: normalizeResponse -> normalizeFindAllResponse -> normalizeArrayResponse -> normalize (called 3 times, once for each record)

This makes the serializer highly customizable and gives you a lot of find-grained control over your payloads. Typically it you need to customize serialization or normalization behavior it’s best to do it at the most “abstract level” possible. For example the suggest I posted above, customizing normalizeArrayResponse, was because your backend probably wraps in an object with a “data” key for any “array” type response (findAll, query, etc). You could obviously make this modification in normalizeQueryResponse and normalizeFindAllResponse but then you’d have to do it in two or more places instead of just one. And if you need to munge individual record items it’s obviously best to do that in normalize.

Anyway, hopefully this is a useful overview, feel free to post any follow up questions if you need more help, and dont forget to look at the docs and eventhe code for reference!