json-api-serializer
A Node.js framework agnostic library for serializing your data to JSON API compliant responses (a specification for building APIs in JSON).
Installation
npm install --save json-api-serializer
Documentation
Register
var JSONAPISerializer = require("json-api-serializer");
var Serializer = new JSONAPISerializer();
Serializer.register(type, options);
Serialization options:
- id (optional): The key to use as the reference. Default = 'id'.
- blacklist (optional): An array of blacklisted attributes. Default = [].
- whitelist (optional): An array of whitelisted attributes. Default = [].
- jsonapiObject (optional): Enable/Disable JSON API Object. Default = true.
- links (optional): Describes the links inside data. It can be:
- An object (values can be string or function).
- A function with one argument
function(data) { ... }
or with two argumentsfunction(data, extraData) { ... }
- topLevelMeta (optional): Describes the top-level meta. It can be:
- An object (values can be string or function).
- A function with one argument
function(extraData) { ... }
or with two argumentsfunction(data, extraData) { ... }
- topLevelLinks (optional): Describes the top-level links. It can be:
- An object (values can be string or function).
- A function with one argument
function(extraData) { ... }
or with two argumentsfunction(data, extraData) { ... }
- meta (optional): Describes resource-level meta. It can be:
- An object (values can be string or function).
- A function with one argument
function(data) { ... }
or with two argumentsfunction(data, extraData) { ... }
- relationships (optional): An object defining some relationships
- relationship: The property in data to use as a relationship
- type: A string or a function
function(relationshipData, data) { ... }
for the type to use for serializing the relationship (type need to be register). - alternativeKey (optional): An alternative key (string or path) to use if relationship key not exist (example: 'author_id' as an alternative key for 'author' relationship). See issue #12.
- schema (optional): A custom schema for serializing the relationship. If no schema define, it use the default one.
- links (optional): Describes the links for the relationship. It can be:
- An object (values can be string or function).
- A function with one argument
function(data) { ... }
or with two argumentsfunction(data, extraData) { ... }
- meta (optional): Describes meta that contains non-standard meta-information about the relationship. It can be:
- An object (values can be string or function).
- A function with one argument
function(data) { ... }
or with two argumentsfunction(data, extraData) { ... }
- deserialize (optional): Describes the function which should be used to deserialize a related property which is not included in the JSON:API document. It should be:
- A function with one argument
function(data) { ... }
which defines the format to which a relation should be deserialized. By default, the ID of the related object is returned, which would be equal tofunction(data) {return data.id}
. See issue #65.
- A function with one argument
- type: A string or a function
- relationship: The property in data to use as a relationship
- convertCase (optional): Case conversion for serializing data. Value can be :
kebab-case
,snake_case
,camelCase
Deserialization options:
- unconvertCase (optional): Case conversion for deserializing data. Value can be :
kebab-case
,snake_case
,camelCase
- blacklistOnDeserialize (optional): An array of blacklisted attributes. Default = [].
- whitelistOnDeserialize (optional): An array of whitelisted attributes. Default = [].
Global options:
To avoid repeating the same options for each type, it's possible to add global options on JSONAPISerializer
instance:
var JSONAPISerializer = require("json-api-serializer");
var Serializer = new JSONAPISerializer({
convertCase: "kebab-case",
unconvertCase: "camelCase"
});
Usage
input data (can be an object or an array of objects)
// Data
var data = [
{
id: "1",
title: "JSON API paints my bikeshed!",
body: "The shortest article. Ever.",
created: "2015-05-22T14:56:29.000Z",
updated: "2015-05-22T14:56:28.000Z",
author: {
id: "1",
firstName: "Kaley",
lastName: "Maggio",
email: "Kaley-Maggio@example.com",
age: "80",
gender: "male"
},
tags: ["1", "2"],
photos: [
"ed70cf44-9a34-4878-84e6-0c0e4a450cfe",
"24ba3666-a593-498c-9f5d-55a4ee08c72e",
"f386492d-df61-4573-b4e3-54f6f5d08acf"
],
comments: [
{
_id: "1",
body: "First !",
created: "2015-08-14T18:42:16.475Z"
},
{
_id: "2",
body: "I Like !",
created: "2015-09-14T18:42:12.475Z"
},
{
_id: "3",
body: "Awesome",
created: "2015-09-15T18:42:12.475Z"
}
]
}
];
Register
Register your resources types :
var JSONAPISerializer = require("json-api-serializer");
var Serializer = new JSONAPISerializer();
// Register 'article' type
Serializer.register("article", {
id: "id", // The attributes to use as the reference. Default = 'id'.
blacklist: ["updated"], // An array of blacklisted attributes. Default = []
links: {
// An object or a function that describes links.
self: function(data) {
// Can be a function or a string value ex: { self: '/articles/1'}
return "/articles/" + data.id;
}
},
relationships: {
// An object defining some relationships.
author: {
type: "people", // The type of the resource
links: function(data) {
// An object or a function that describes Relationships links
return {
self: "/articles/" + data.id + "/relationships/author",
related: "/articles/" + data.id + "/author"
};
}
},
tags: {
type: "tag"
},
photos: {
type: "photo"
},
comments: {
type: "comment",
schema: "only-body" // A custom schema
}
},
topLevelMeta: function(data, extraData) {
// An object or a function that describes top level meta.
return {
count: extraData.count,
total: data.length
};
},
topLevelLinks: {
// An object or a function that describes top level links.
self: "/articles" // Can be a function (with extra data argument) or a string value
}
});
// Register 'people' type
Serializer.register("people", {
id: "id",
links: {
self: function(data) {
return "/peoples/" + data.id;
}
}
});
// Register 'tag' type
Serializer.register("tag", {
id: "id"
});
// Register 'photo' type
Serializer.register("photo", {
id: "id"
});
// Register 'comment' type with a custom schema
Serializer.register("comment", "only-body", {
id: "_id"
});
Serialize
Serialize it with the corresponding resource type, data and optional extra data :
// Synchronously (blocking)
const result = Serializer.serialize('article', data, {count: 2});
// Asynchronously (non-blocking)
Serializer.serializeAsync('article', data, {count: 2})
.then((result) => {
...
});
The output data will be :
{
"jsonapi": {
"version": "1.0"
},
"meta": {
"count": 2,
"total": 1
},
"links": {
"self": "/articles"
},
"data": [{
"type": "article",
"id": "1",
"attributes": {
"title": "JSON API paints my bikeshed!",
"body": "The shortest article. Ever.",
"created": "2015-05-22T14:56:29.000Z"
},
"relationships": {
"author": {
"data": {
"type": "people",
"id": "1"
},
"links": {
"self": "/articles/1/relationships/author",
"related": "/articles/1/author"
}
},
"tags": {
"data": [{
"type": "tag",
"id": "1"
}, {
"type": "tag",
"id": "2"
}]
},
"photos": {
"data": [{
"type": "photo",
"id": "ed70cf44-9a34-4878-84e6-0c0e4a450cfe"
}, {
"type": "photo",
"id": "24ba3666-a593-498c-9f5d-55a4ee08c72e"
}, {
"type": "photo",
"id": "f386492d-df61-4573-b4e3-54f6f5d08acf"
}]
},
"comments": {
"data": [{
"type": "comment",
"id": "1"
}, {
"type": "comment",
"id": "2"
}, {
"type": "comment",
"id": "3"
}]
}
},
"links": {
"self": "/articles/1"
}
}],
"included": [{
"type": "people",
"id": "1",
"attributes": {
"firstName": "Kaley",
"lastName": "Maggio",
"email": "Kaley-Maggio@example.com",
"age": "80",
"gender": "male"
},
"links": {
"self": "/peoples/1"
}
}, {
"type": "comment",
"id": "1",
"attributes": {
"body": "First !"
}
}, {
"type": "comment",
"id": "2",
"attributes": {
"body": "I Like !"
}
}, {
"type": "comment",
"id": "3",
"attributes": {
"body": "Awesome"
}
}]
}
Some others examples are available in tests folders
Deserialize
input data (can be an simple object or an array of objects)
var data = {
data: {
type: 'article',
id: '1',
attributes: {
title: 'JSON API paints my bikeshed!',
body: 'The shortest article. Ever.',
created: '2015-05-22T14:56:29.000Z'
},
relationships: {
author: {
data: {
type: 'people',
id: '1'
}
},
comments: {
data: [{
type: 'comment',
id: '1'
}, {
type: 'comment',
id: '2'
}]
}
}
}
};
// Synchronously (blocking)
Serializer.deserialize('article', data);
// Asynchronously (non-blocking)
Serializer.deserializeAsync('article', data)
.then((result) => {
// ...
});
{
"id": "1",
"title": "JSON API paints my bikeshed!",
"body": "The shortest article. Ever.",
"created": "2015-05-22T14:56:29.000Z",
"author": "1",
"comments": [
"1",
"2"
]
}
serializeError
Serializes any error into a JSON API error document.
Input data can be:
- An instance of Error or an array of instance of Error.
- A JSON API error object or an array of JSON API error object.
const error = new Error('An error occured');
error.status = 500;
Serializer.serializeError(error);
The result will be:
{
"errors": [
{
"status": "500",
"detail": "An error occured"
}
]
}
Custom schemas
It is possible to define multiple custom schemas for a resource type :
Serializer.register(type, "customSchema", options);
Then you can apply this schema on the primary data when serialize or deserialize :
Serializer.serialize("article", data, "customSchema", { count: 2 });
Serializer.serializeAsync("article", data, "customSchema", { count: 2 });
Serializer.deserialize("article", jsonapiData, "customSchema");
Serializer.deserializeAsync("article", jsonapiData, "customSchema");
Or if you want to apply this schema on a relationship data, define this schema on relationships options with the key schema
:
Example :
relationships: {
comments: {
type: "comment";
schema: "customSchema";
}
}
Mixed data (dynamic type)
Serialize
If your data contains one or multiple objects of different types, it's possible to define a configuration object instead of the type-string as the first argument of serialize
and serializeAsync
with these options:
- type (required): A string for the path to the key to use to determine type or a function deriving a type-string from each data-item.
- jsonapiObject (optional): Enable/Disable JSON API Object. Default = true.
- topLevelMeta (optional): Describes the top-level meta. It can be:
- An object (values can be string or function).
- A function with one argument
function(extraData) { ... }
or with two argumentsfunction(data, extraData) { ... }
- topLevelLinks (optional): Describes the top-level links. It can be:
- An object (values can be string or function).
- A function with one argument
function(extraData) { ... }
or with two argumentsfunction(data, extraData) { ... }
Example :
const typeConfig = {
// Same as type: 'type'
type: data => data.type // Can be very complex to determine different types of items.
};
Serializer.serializeAsync(typeConfig, data, { count: 2 }).then(result => {
// ...
});
Deserialize
If your data contains one or multiple objects of different types, it's possible to define a configuration object instead of the type-string as the first argument of deserialize
with these options:
- type (required): A string for the path to the key to use to determine type or a function deriving a type-string from each data-item.
Example :
const typeConfig = {
// Same as type: 'type'
type: data => data.type // Can be very complex to determine different types of items.
};
const deserialized = Serializer.deserializeAsync(typeConfig, data).then(result => {
// ...
});
Benchmark
Platform info:
==============
Darwin 18.5.0 x64
Node.JS: 10.15.2
V8: 6.8.275.32-node.12
Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz × 8
Suite:
==============
serializeAsync x 64,411 ops/sec ±0.59% (82 runs sampled)
serialize x 150,408 ops/sec ±0.63% (93 runs sampled)
deserializeAsync x 111,881 ops/sec ±0.31% (84 runs sampled)
deserialize x 445,711 ops/sec ±0.29% (94 runs sampled)
serializeError x 294,273 ops/sec ±0.79% (90 runs sampled)
serializeError with a JSON API error object x 16,864,514 ops/sec ±0.32% (93 runs sampled)