Skip to content

Commit 9e5a62d

Browse files
committed
initial commit
0 parents  commit 9e5a62d

11 files changed

+647
-0
lines changed

.editorconfig

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# EditorConfig helps developers define and maintain consistent
2+
# coding styles between different editors and IDEs
3+
# http://editorconfig.org/
4+
5+
root = true
6+
7+
[*]
8+
charset = utf-8
9+
end_of_line = lf
10+
indent_size = 2
11+
indent_style = space
12+
insert_final_newline = true
13+
trim_trailing_whitespace = true
14+
15+
[*.md]
16+
trim_trailing_whitespace = false

.eslintignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules/
2+
coverage/
3+
test/**/*.js

.eslintrc

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"extends": "airbnb/base",
3+
"env": {
4+
"node": true,
5+
"mocha": true
6+
},
7+
"rules": {
8+
"space-before-function-paren": 0,
9+
"func-names": 0,
10+
"object-shorthand": 0,
11+
"no-param-reassign": 0,
12+
"prefer-template": 0,
13+
"max-len": [2, 200, 4, {
14+
"ignoreComments": true,
15+
"ignoreUrls": true
16+
}]
17+
}
18+
}

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
coverage/

LICENCE

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2016 Kévin Danielo
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of
6+
this software and associated documentation files (the "Software"), to deal in
7+
the Software without restriction, including without limitation the rights to
8+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9+
of the Software, and to permit persons to whom the Software is furnished to do
10+
so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# json-api-serializer
2+
[![Build Status](https://travis-ci.org/danivek/json-api-serializer.svg?branch=master)](https://travis-ci.org/danivek/json-api-serializer)
3+
[![Coverage Status](https://coveralls.io/repos/danivek/json-api-serializer/badge.svg?branch=master&service=github)](https://coveralls.io/github/danivek/json-api-serializer?branch=master)
4+
5+
A Node.js framework agnostic library for serializing your data to [JSON API](http://jsonapi.org/) compliant responses (a specification for building APIs in JSON).
6+
7+
***Why another library for serializing data to JSON API ?***
8+
9+
Simply because others libraries are not as flexible as i need.
10+
11+
12+
## Installation
13+
```bash
14+
npm install --save json-api-serializer
15+
```
16+
17+
## Usage
18+
19+
WIP
20+
21+
json-api-serializer is currently under development.
22+
23+
Some examples are available in [unit tests](https://github.com/danivek/json-api-serializer/blob/master/test/unit/JSONAPISerializer.test.js)
24+
25+
More examples will coming soon.
26+
27+
## Requirements
28+
29+
json-api-serializer only use ECMAScript 2015 (ES6) features supported natively by Node.js 4 and above ([ECMAScript 2015 (ES6) | Node.js](https://nodejs.org/en/docs/es6/)). Make sure that you have Node.js 4+ or above.
30+
31+
## License
32+
33+
[MIT](https://github.com/danivek/json-api-serializer/blob/master/LICENSE)

index.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('./lib/JSONAPISerializer');

lib/JSONAPISerializer.js

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
'use strict';
2+
3+
const _ = require('lodash');
4+
const joi = require('joi');
5+
6+
module.exports = class JSONAPISerializer {
7+
constructor(opts) {
8+
this.opts = opts || {};
9+
this.schemas = {};
10+
}
11+
12+
validateOptions(options) {
13+
const optionsSchema = joi.object({
14+
blackList: joi.array().items(joi.string()).single().default([]),
15+
id: joi.string().default('id'),
16+
relationships: joi.object().pattern(/.+/, joi.object({
17+
type: joi.string().required(),
18+
})).default({}),
19+
}).required();
20+
21+
const validated = joi.validate(options, optionsSchema);
22+
23+
if (validated.error) {
24+
throw new Error(validated.error);
25+
}
26+
27+
return validated.value;
28+
}
29+
30+
register(type, schemaName, options) {
31+
if (_.isObject(schemaName)) {
32+
options = schemaName;
33+
schemaName = 'default';
34+
}
35+
36+
const name = schemaName || 'default';
37+
const opts = this.validateOptions(_.defaults({}, options));
38+
39+
_.set(this.schemas, [type, name].join('.'), opts);
40+
}
41+
42+
serialize(type, data, schemaName) {
43+
const schema = schemaName || 'default';
44+
45+
if (!this.schemas[type]) {
46+
throw new Error('No type registered for ' + type);
47+
}
48+
49+
if (schema && !this.schemas[type][schema]) {
50+
throw new Error('No schema ' + schema + ' registered for ' + type);
51+
}
52+
53+
const included = [];
54+
return _.assign({}, {
55+
jsonapi: {
56+
version: '1.0',
57+
},
58+
links: this.getLinks(this.opts, this.schemas[type][schema].topLevelLinks),
59+
data: this.serializeData(type, data, this.schemas[type][schema], included),
60+
included: this.serializeIncluded(included),
61+
});
62+
}
63+
64+
serializeData(type, data, options, included) {
65+
// Empty data
66+
if (_.isEmpty(data)) {
67+
// Respond [] or null
68+
return _.isArray(data) ? data : null;
69+
}
70+
71+
// Array data
72+
if (_.isArray(data)) {
73+
return data.map(d => this.serializeData(type, d, options, included));
74+
}
75+
76+
// Single data
77+
const resource = {
78+
type: type,
79+
id: data[options.id],
80+
attributes: this.serializeAttributes(data, options),
81+
relationships: this.serializeRelationships(data, options, included),
82+
links: this.getLinks(data, options.links),
83+
};
84+
85+
return resource;
86+
}
87+
88+
serializeIncluded(included) {
89+
const serializedIncluded = _.uniqWith(included, _.isEqual);
90+
return !_.isEmpty(serializedIncluded) ? serializedIncluded : undefined;
91+
}
92+
93+
serializeAttributes(data, options) {
94+
return _.pick(data, _.difference(Object.keys(data), _.concat([options.id], Object.keys(options.relationships), options.blackList)));
95+
}
96+
97+
serializeRelationships(data, options, included) {
98+
const serializedRelationships = {};
99+
100+
_.forOwn(options.relationships, (rOptions, relationship) => {
101+
const serializeRelationship = {
102+
data: this.serializeRelationship(rOptions.type, data[relationship], this.schemas[options.relationships[relationship].type].default, included),
103+
links: this.getLinks(data, rOptions.links),
104+
};
105+
_.set(serializedRelationships, relationship, serializeRelationship);
106+
});
107+
108+
return !_.isEmpty(serializedRelationships) ? serializedRelationships : undefined;
109+
}
110+
111+
serializeRelationship(rType, rData, rOptions, included) {
112+
// To-many relationships
113+
if (_.isArray(rData)) {
114+
return rData.map(d => this.serializeRelationship(rType, d, rOptions, included));
115+
}
116+
117+
// To-one relationship
118+
const serializedRelationship = {
119+
type: rType,
120+
};
121+
122+
// support for unpopulated relationships (an id, or array of ids)
123+
if (!_.isPlainObject(rData)) {
124+
// Relationship has not been populated
125+
serializedRelationship.id = rData;
126+
} else {
127+
// Relationship has been populated
128+
serializedRelationship.id = rData[rOptions.id];
129+
included.push(this.serializeData(rType, rData, rOptions));
130+
}
131+
132+
return serializedRelationship;
133+
}
134+
135+
getLinks(data, linksOptions) {
136+
const links = _.mapValues(linksOptions, linkOpts => {
137+
let link;
138+
if (_.isFunction(linkOpts)) {
139+
link = linkOpts(data);
140+
} else {
141+
link = linkOpts;
142+
}
143+
return link;
144+
});
145+
146+
return !_.isEmpty(links) ? links : undefined;
147+
}
148+
};

package.json

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "json-api-serializer",
3+
"version": "0.1.0",
4+
"description": "Framework agnostic JSON API serializer.",
5+
"main": "index.js",
6+
"scripts": {
7+
"lint": "./node_modules/.bin/eslint .",
8+
"test": "./node_modules/.bin/istanbul cover --print both ./node_modules/mocha/bin/_mocha -- -R spec ./test/**/*.js",
9+
"coveralls": "cat ./coverage/lcov.info | node ./node_modules/coveralls/bin/coveralls.js"
10+
},
11+
"keywords": [
12+
"json",
13+
"json-api",
14+
"jsonapi",
15+
"json api",
16+
"serializer"
17+
],
18+
"author": "Kévin Danielo",
19+
"repository": "danivek/json-api-serializer",
20+
"license": "MIT",
21+
"engines": {
22+
"node": ">= 4.0.0"
23+
},
24+
"devDependencies": {
25+
"chai": "^3.5.0",
26+
"coveralls": "^2.11.8",
27+
"eslint": "^1.10.3",
28+
"eslint-config-airbnb": "^5.0.0",
29+
"istanbul": "^0.4.2",
30+
"mocha": "^2.4.5"
31+
},
32+
"dependencies": {
33+
"joi": "^8.0.3",
34+
"lodash": "^4.5.1"
35+
}
36+
}

0 commit comments

Comments
 (0)