A Node.js framework agnostic library for serializing your data to JSON API compliant responses (a specification for building APIs in JSON).
npm install --save json-api-serializer
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:
When using convertCase, a LRU cache is utilized for optimization. The default size of the cache is 5000 per conversion type. The size of the cache can be set with the convertCaseCacheSize
option. Passing in 0 will result in a LRU cache of infinite size.
var JSONAPISerializer = require("json-api-serializer");
var Serializer = new JSONAPISerializer({
convertCase: "kebab-case",
unconvertCase: "camelCase",
convertCaseCacheSize: 0
});
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 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 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
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"
]
}
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"
}
]
}
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";
}
}
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 => {
// ...
});
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 => {
// ...
});
Platform info:
==============
Darwin 18.7.0 x64
Node.JS: 10.16.3
V8: 6.8.275.32-node.54
Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz × 8
Suite:
==============
serializeAsync x 80,043 ops/sec ±0.74% (78 runs sampled)
serialize x 135,669 ops/sec ±1.12% (88 runs sampled)
serializeConvertCase x 98,785 ops/sec ±2.34% (88 runs sampled)
deserializeAsync x 172,832 ops/sec ±0.41% (82 runs sampled)
deserialize x 393,979 ops/sec ±0.32% (91 runs sampled)
deserializeConvertCase x 119,021 ops/sec ±1.76% (95 runs sampled)
serializeError x 276,346 ops/sec ±1.07% (86 runs sampled)
serializeError with a JSON API error object x 15,783,113 ops/sec ±1.74% (88 runs sampled)