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.