Skip to content

Commit 0a72573

Browse files
committed
add deserialize method
1 parent 0d95058 commit 0a72573

File tree

3 files changed

+271
-7
lines changed

3 files changed

+271
-7
lines changed

README.md

+60-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# json-api-serializer
22
[![Build Status](https://travis-ci.org/danivek/json-api-serializer.svg?branch=master)](https://travis-ci.org/danivek/json-api-serializer)
33
[![Coverage Status](https://coveralls.io/repos/github/danivek/json-api-serializer/badge.svg?branch=master)](https://coveralls.io/github/danivek/json-api-serializer?branch=master)
4-
[![npm](https://img.shields.io/npm/v/json-api-serializer.svg?maxAge=2592000)](https://www.npmjs.org/package/json-api-serializer)
4+
[![npm](https://img.shields.io/npm/v/json-api-serializer.svg)](https://www.npmjs.org/package/json-api-serializer)
55

66
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).
77

@@ -38,11 +38,13 @@ Serializer.register(type, options);
3838
- **alternativeKey** (optional): An alternative key to use if relationship key not exist (example: 'author_id' as an alternative key for 'author' relationship). See [issue #12](https://github.com/danivek/json-api-serializer/issues/12).
3939
- **schema** (optional): A custom schema for serializing the relationship. If no schema define, it use the default one.
4040
- **links** (optional): An *object* or a *function* that describes the links for the relationship. (If it is an object values can be string or function).
41-
- **convertCase** (optional): Case conversion for outputted data. Value can be : `kebab-case`, `snake_case`, `camelCase`
41+
- **convertCase** (optional): Case conversion for serializing data. Value can be : `kebab-case`, `snake_case`, `camelCase`
42+
- **unconvertCase** (optional): Case conversion for deserializing data. Value can be : `kebab-case`, `snake_case`, `camelCase`
43+
4244

4345
## Usage
4446

45-
input data (can be a simple object or an array of objects)
47+
input data (can be an object or an array of objects)
4648
```javascript
4749
// Data
4850
var data = {
@@ -77,6 +79,7 @@ var data = {
7779
}
7880
```
7981

82+
### Register
8083
Register your resources types :
8184
```javascript
8285
var JSONAPISerializer = require('json-api-serializer');
@@ -149,6 +152,8 @@ Serializer.register('comment', 'only-body', {
149152
});
150153
```
151154

155+
### Serialize
156+
152157
Serialize it with the corresponding resource type, data and optional extra options :
153158

154159
```javascript
@@ -260,6 +265,56 @@ The output data will be :
260265
```
261266
Some others examples are available in [ tests folders](https://github.com/danivek/json-api-serializer/blob/master/test/)
262267

268+
### Deserialize
269+
270+
```javascript
271+
var data = {
272+
data: {
273+
type: 'article',
274+
id: '1',
275+
attributes: {
276+
title: 'JSON API paints my bikeshed!',
277+
body: 'The shortest article. Ever.',
278+
created: '2015-05-22T14:56:29.000Z'
279+
},
280+
relationships: {
281+
author: {
282+
data: {
283+
type: 'people',
284+
id: '1'
285+
}
286+
},
287+
comments: {
288+
data: [{
289+
type: 'comment',
290+
id: '1'
291+
}, {
292+
type: 'comment',
293+
id: '2'
294+
}]
295+
}
296+
}
297+
}
298+
};
299+
300+
Serializer.deserialize('article', data);
301+
```
302+
303+
```JSON
304+
{
305+
"id": "1",
306+
"title": "JSON API paints my bikeshed!",
307+
"body": "The shortest article. Ever.",
308+
"created": "2015-05-22T14:56:29.000Z",
309+
"author": "1",
310+
"comments": [
311+
"1",
312+
"2"
313+
]
314+
}
315+
```
316+
317+
263318
## Custom schemas
264319

265320
It is possible to define multiple custom schemas for a resource type :
@@ -268,10 +323,11 @@ It is possible to define multiple custom schemas for a resource type :
268323
Serializer.register(type, 'customSchema', options);
269324
```
270325

271-
If you want to apply this schema on the primary data :
326+
Then you can apply this schema on the primary data when serialize or deserialize :
272327

273328
```javascript
274329
Serializer.serialize('article', data, 'customSchema', {count: 2});
330+
Serializer.deserialize('article', jsonapiData, 'customSchema');
275331
```
276332

277333
Or if you want to apply this schema on a relationship data, define this schema on relationships options with the key `schema` :

lib/JSONAPISerializer.js

+55
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ module.exports = class JSONAPISerializer {
4444
topLevelLinks: joi.alternatives([joi.func(), joi.object()]).default({}),
4545
topLevelMeta: joi.alternatives([joi.func(), joi.object()]).default({}),
4646
convertCase: joi.string().valid('kebab-case', 'snake_case', 'camelCase'),
47+
unconvertCase: joi.string().valid('kebab-case', 'snake_case', 'camelCase'),
4748
}).required();
4849

4950
const validated = joi.validate(options, optionsSchema);
@@ -121,6 +122,60 @@ module.exports = class JSONAPISerializer {
121122
};
122123
}
123124

125+
/**
126+
* Deserialize JSON API document data.
127+
* Input data can be a simple object or an array of objects.
128+
*
129+
* @method JSONAPISerializer#deserialize
130+
* @param {string} type resource's type.
131+
* @param {Object} data JSON API input data.
132+
* @param {string} [schema=default] resource's schema name.
133+
* @return {Object} deserialized data.
134+
*/
135+
deserialize(type, data, schema) {
136+
schema = schema || 'default';
137+
138+
if (!this.schemas[type]) {
139+
throw new Error('No type registered for ' + type);
140+
}
141+
142+
if (schema && !this.schemas[type][schema]) {
143+
throw new Error('No schema ' + schema + ' registered for ' + type);
144+
}
145+
146+
const resourceOpts = this.schemas[type][schema];
147+
let deserializedData = {};
148+
149+
if (data.data) {
150+
// Deserialize id
151+
deserializedData[resourceOpts.id] = data.data.id || undefined;
152+
153+
// Deserialize attributes
154+
Object.assign(deserializedData, data.data.attributes);
155+
156+
// Deserialize relationships
157+
_.forOwn(data.data.relationships, (value, relationship) => {
158+
// Support alternativeKey options for relationships
159+
let relationshipKey = relationship;
160+
if (resourceOpts.relationships[relationshipKey] && resourceOpts.relationships[relationshipKey].alternativeKey) {
161+
relationshipKey = resourceOpts.relationships[relationshipKey].alternativeKey;
162+
}
163+
164+
if (_.isArray(value.data)) {
165+
deserializedData[relationshipKey] = value.data.map((d) => d.id);
166+
} else {
167+
deserializedData[relationshipKey] = value.data.id;
168+
}
169+
});
170+
171+
if (resourceOpts.unconvertCase) {
172+
deserializedData = this._convertCase(deserializedData, resourceOpts.unconvertCase);
173+
}
174+
}
175+
176+
return deserializedData;
177+
}
178+
124179
/**
125180
* Serialize resource objects.
126181
*

test/unit/JSONAPISerializer.test.js

+156-3
Original file line numberDiff line numberDiff line change
@@ -623,9 +623,9 @@ describe('JSONAPISerializer', function() {
623623
});
624624

625625
const data = {
626-
id: "1",
627-
title: "JSON API paints my bikeshed!",
628-
body: "The shortest article. Ever."
626+
id: '1',
627+
title: 'JSON API paints my bikeshed!',
628+
body: 'The shortest article. Ever.'
629629
};
630630

631631
const serializedData = Serializer.serialize('articles', data, 'only-title');
@@ -652,4 +652,157 @@ describe('JSONAPISerializer', function() {
652652
done();
653653
});
654654
});
655+
656+
describe('deserialize', function() {
657+
it('should deserialize data with relationships', function(done) {
658+
const Serializer = new JSONAPISerializer();
659+
Serializer.register('articles', {});
660+
661+
const data = {
662+
data: {
663+
type: 'article',
664+
id: '1',
665+
attributes: {
666+
title: 'JSON API paints my bikeshed!',
667+
body: 'The shortest article. Ever.',
668+
created: '2015-05-22T14:56:29.000Z'
669+
},
670+
relationships: {
671+
author: {
672+
data: {
673+
type: 'people',
674+
id: '1'
675+
}
676+
},
677+
comments: {
678+
data: [{
679+
type: 'comment',
680+
id: '1'
681+
}, {
682+
type: 'comment',
683+
id: '2'
684+
}]
685+
}
686+
}
687+
}
688+
};
689+
690+
const deserializedData = Serializer.deserialize('articles', data);
691+
expect(deserializedData).to.have.property('id');
692+
expect(deserializedData).to.have.property('title');
693+
expect(deserializedData).to.have.property('body');
694+
expect(deserializedData).to.have.property('created');
695+
expect(deserializedData).to.have.property('author', '1');
696+
expect(deserializedData).to.have.property('comments').to.be.instanceof(Array).to.eql(['1', '2']);
697+
done();
698+
});
699+
700+
it('should deserialize with \'id\' options', function(done) {
701+
const Serializer = new JSONAPISerializer();
702+
Serializer.register('articles', {
703+
id: '_id'
704+
});
705+
706+
const data = {
707+
data: {
708+
type: 'article',
709+
id: '1',
710+
attributes: {
711+
title: 'JSON API paints my bikeshed!',
712+
body: 'The shortest article. Ever.',
713+
created: '2015-05-22T14:56:29.000Z'
714+
}
715+
}
716+
};
717+
718+
const deserializedData = Serializer.deserialize('articles', data);
719+
expect(deserializedData).to.have.property('_id');
720+
expect(deserializedData).to.not.have.property('id');
721+
done();
722+
});
723+
724+
it('should deserialize with \'alternativeKey\' options', function(done) {
725+
const Serializer = new JSONAPISerializer();
726+
Serializer.register('articles', {
727+
relationships: {
728+
author: {
729+
type: 'people',
730+
alternativeKey: 'author_id'
731+
}
732+
}
733+
});
734+
735+
const data = {
736+
data: {
737+
type: 'article',
738+
id: '1',
739+
attributes: {
740+
title: 'JSON API paints my bikeshed!',
741+
body: 'The shortest article. Ever.',
742+
created: '2015-05-22T14:56:29.000Z'
743+
},
744+
relationships: {
745+
author: {
746+
data: {
747+
type: 'people',
748+
id: '1'
749+
}
750+
}
751+
}
752+
}
753+
};
754+
755+
const deserializedData = Serializer.deserialize('articles', data);
756+
expect(deserializedData).to.have.property('author_id');
757+
expect(deserializedData).to.not.property('author');
758+
done();
759+
});
760+
761+
it('should deserialize with \'unconvertCase\' options', function(done) {
762+
const Serializer = new JSONAPISerializer();
763+
Serializer.register('articles', {
764+
unconvertCase: 'snake_case'
765+
});
766+
767+
const data = {
768+
data: {
769+
type: 'article',
770+
id: '1',
771+
attributes: {
772+
createdAt: '2015-05-22T14:56:29.000Z'
773+
},
774+
relationships: {
775+
articleAuthor: {
776+
data: {
777+
type: 'people',
778+
id: '1'
779+
}
780+
}
781+
}
782+
}
783+
};
784+
785+
const deserializedData = Serializer.deserialize('articles', data);
786+
expect(deserializedData).to.have.property('created_at');
787+
expect(deserializedData).to.have.property('article_author');
788+
done();
789+
});
790+
791+
it('should throw an error if type as not been registered', function(done) {
792+
expect(function() {
793+
const Serializer = new JSONAPISerializer();
794+
Serializer.deserialize('authors', {});
795+
}).to.throw(Error, 'No type registered for authors');
796+
done();
797+
});
798+
799+
it('should throw an error if custom schema as not been registered', function(done) {
800+
expect(function() {
801+
const Serializer = new JSONAPISerializer();
802+
Serializer.register('articles', {});
803+
Serializer.deserialize('articles', {}, 'custom');
804+
}).to.throw(Error, 'No schema custom registered for articles');
805+
done();
806+
});
807+
});
655808
});

0 commit comments

Comments
 (0)