Extracting Metadata from a Custom API with Ember Data

Last reviewed on April 6, 2018

When querying an API, sometimes metadata is returned such as pagination information. For example:

{
  "post": [
    { "id": 1, "title": "Post 1" },
    { "id": 2, "title": "Post 2" },
    { "id": 3, "title": "Post 3" }
  ],
  "meta": {
    "total": 100
  }
}

With the response above, we can access meta off of the result of our store.query() call:

import Route from '@ember/routing/route';

export default class IndexRoute extends Route {
  model() {
    return this.store.query('post', { page: 1 });
  }

  afterModel(posts) {
    console.log('Meta: ', posts.get('meta')); // { total: 100 }
  }
}

Great, but what if our API isn’t structured like above? Let’s say we have something like this instead:

{
  "total": 100,
  "items": [
    { "id": 1, "title": "Post 1" },
    { "id": 2, "title": "Post 2" },
    { "id": 3, "title": "Post 3" }
  ]
}

This response doesn’t match what any of the built-in serializers expect. If you’re not familiar with the different Ember Data serializers, check out my other post, Which Ember Data Serializer Should I Use?.

Let’s try normalizing this response for JSONSerializer:

import JSONSerializer from '@ember-data/serializer/json';

export default class ApplicationSerializer extends JSONSerializer {
  normalizeQueryResponse(store, ModelClass, payload, id, requestName) {
    return super.normalizeQueryResponse(
      store,
      ModelClass,
      payload.items,
      id,
      requestName
    );
  }
}

Cool, that works. Next, let’s extract the metadata. Looking at the API documentation for JSONSerializer, my first thought was to use the extractMeta(store, ModelClass, payload) method. I tried something like this:

import JSONSerializer from '@ember-data/serializer/json';

export default class ApplicationSerializer extends JSONSerializer {
  normalizeQueryResponse(store, ModelClass, payload, id, requestName) {
    return super.normalizeQueryResponse(
      store,
      ModelClass,
      payload.items,
      id,
      requestName
    );
  }

  extractMeta(store, ModelClass, payload) {
    console.log('payload', payload);
  }
}

Unfortunately, the payload logged to the console in extractMeta was the following:

[
  { "id": 1, "title": "Post 1" },
  { "id": 2, "title": "Post 2" },
  { "id": 3, "title": "Post 3" }
]

No sight of that total property. It turns out, extractMeta() gets called after normalizeQueryResponse(), so payload in extractMeta isn’t the original payload; it is the normalized one.

So how can we make this work?

One approach is to extend RESTSerializer instead of JSONSerializer:

import RESTSerializer from '@ember-data/serializer/rest';

export default class ApplicationSerializer extends RESTSerializer {
  normalizeQueryResponse(store, ModelClass, payload, id, requestName) {
    payload[ModelClass.modelName] = payload.items;
    delete payload.items;
    return super.normalizeQueryResponse(...arguments);
  }

  extractMeta(store, ModelClass, payload) {
    let { total } = payload;
    payload.meta = { total };
    delete payload.total;
    return super.extractMeta(...arguments);
  }
}

Here we are normalizing the payload to match what RESTSerializer expects, which involves changing the items key to post. The normalized payload gets passed into extractMeta, which still has total. We can then assign total as a property in a meta object.

A second approach is to use JSONSerializer as follows:

import JSONSerializer from '@ember-data/serializer/json';

export default class ApplicationSerializer extends JSONSerializer {
  normalizeQueryResponse(store, ModelClass, payload, id, requestName) {
    let normalized = super.normalizeQueryResponse(
      store,
      ModelClass,
      payload.items,
      id,
      requestName
    );

    normalized.meta = this.extractMeta(store, ModelClass, payload);
    return normalized;
  }

  extractMeta(store, ModelClass, payload) {
    let meta = {
      total: payload.total,
    };

    return meta;
  }
}

In this implementation with JSONSerializer, the payload is first normalized into JSON:API since Ember Data uses that internally. Then, we can call extractMeta ourselves with the raw payload and assign the result as the meta property on the normalized payload.

Here is the code from this post.

Thanks to @Runspired for helping me with the JSONSerializer implementation.


Pro Ember Data cover
Pro Ember Data available now!

Are you struggling with Ember Data?

In my new book Pro Ember Data, you will learn how to work with Ember Data efficiently, from APIs, adapters, and serializers to polymorphic relationships, using your existing JavaScript and Ember knowledge. This book will teach you how to adapt Ember Data to fit your custom API.

What You'll Learn

  • Review the differences between normalization and serialization
  • Understand how the built-in adapters and serializers in Ember Data
  • Understand how the built-in adapters and serializers in Ember Data work
  • Customize adapters and serializers to consume any API and write them from scratch
  • Handle API errors in Ember Data
  • Work with the Reddit API using Ember Data
  • Learn how to use polymorphic relationships
Check out Pro Ember Data on Amazon using my affiliate link
You can also find Pro Ember Data on Apress.