Skip to content

Commit 06f10b0

Browse files
authored
deserialized mixed data (#36)
#34
1 parent 4aa9bc1 commit 06f10b0

File tree

3 files changed

+140
-9
lines changed

3 files changed

+140
-9
lines changed

README.md

+20-2
Original file line numberDiff line numberDiff line change
@@ -378,9 +378,11 @@ relationships: {
378378
}
379379
```
380380

381-
## Serialize mixed data (dynamic type)
381+
## Mixed data (dynamic type)
382382

383-
If your data contains one or multiple objects of varying 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:
383+
### Serialize
384+
385+
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:
384386

385387
- **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.
386388
- **topLevelMeta** (optional): Describes the top-level meta. It can be:
@@ -403,6 +405,22 @@ Serializer.serializeAsync(typeConfig, data, {count: 2})
403405
});
404406
```
405407

408+
### Deserialize
409+
410+
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:
411+
412+
- **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.
413+
414+
Example :
415+
```javascript
416+
const typeConfig = {
417+
// Same as type: 'type'
418+
type: (data) => data.type, // Can be very complex to determine different types of items.
419+
};
420+
421+
const deserialized = Serializer.deserialize(typeConfig, data);
422+
```
423+
406424
## Requirements
407425

408426
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.

lib/JSONAPISerializer.js

+25-7
Original file line numberDiff line numberDiff line change
@@ -268,20 +268,24 @@ module.exports = class JSONAPISerializer {
268268
* Input data can be a simple object or an array of objects.
269269
*
270270
* @method JSONAPISerializer#deserialize
271-
* @param {string} type resource's type.
271+
* @param {string|Object} type resource's type as string or an object with a dynamic type resolved from data.
272272
* @param {Object} data JSON API input data.
273273
* @param {string} [schema=default] resource's schema name.
274274
* @return {Object} deserialized data.
275275
*/
276276
deserialize(type, data, schema) {
277277
schema = schema || 'default';
278278

279-
if (!this.schemas[type]) {
280-
throw new Error(`No type registered for ${type}`);
281-
}
279+
if (!_.isPlainObject(type)) {
280+
if (!this.schemas[type]) {
281+
throw new Error(`No type registered for ${type}`);
282+
}
282283

283-
if (schema && !this.schemas[type][schema]) {
284-
throw new Error(`No schema ${schema} registered for ${type}`);
284+
if (schema && !this.schemas[type][schema]) {
285+
throw new Error(`No schema ${schema} registered for ${type}`);
286+
}
287+
} else {
288+
type = this.validateDynamicTypeOptions(type);
285289
}
286290

287291
let deserializedData = {};
@@ -302,12 +306,26 @@ module.exports = class JSONAPISerializer {
302306
* Input data must be a simple object.
303307
*
304308
* @method JSONAPISerializer#deserializeResource
305-
* @param {string} type resource's type.
309+
* @param {string|Object} type resource's type as string or an object with a dynamic type resolved from data.
306310
* @param {Object} data JSON API resource data.
307311
* @param {string} [schema=default] resource's schema name.
308312
* @return {Object} deserialized data.
309313
*/
310314
deserializeResource(type, data, schema, included) {
315+
if (_.isPlainObject(type)) {
316+
type = (typeof type.type === 'function') ? type.type(data) : _.get(data, type.type);
317+
318+
if (!type) {
319+
throw new Error(`No type can be resolved from data: ${JSON.stringify(data)}`);
320+
}
321+
322+
if (!this.schemas[type]) {
323+
throw new Error(`No type registered for ${type}`);
324+
}
325+
326+
schema = 'default';
327+
}
328+
311329
const resourceOpts = this.schemas[type][schema];
312330

313331
let deserializedData = {};

test/unit/JSONAPISerializer.test.js

+95
Original file line numberDiff line numberDiff line change
@@ -1496,4 +1496,99 @@ describe('JSONAPISerializer', function() {
14961496
done();
14971497
});
14981498
});
1499+
1500+
describe('deserializeMixedData', function() {
1501+
const Serializer = new JSONAPISerializer();
1502+
Serializer.register('article');
1503+
Serializer.register('people');
1504+
const typeOption = {type: 'type'};
1505+
1506+
it('should return error if no type can be resolved from data', function(done) {
1507+
const singleData = {
1508+
data: {
1509+
id: '1'
1510+
}
1511+
};
1512+
1513+
expect(function() {
1514+
Serializer.deserialize(typeOption, singleData);
1515+
}).to.throw(Error, 'No type can be resolved from data: {"id":"1"}');
1516+
done();
1517+
});
1518+
1519+
it('should return error if type has not been registered', function(done) {
1520+
const singleData = {
1521+
data: {
1522+
id: '1',
1523+
type: 'book'
1524+
}
1525+
};
1526+
1527+
expect(function() {
1528+
Serializer.deserialize(typeOption, singleData);
1529+
}).to.throw(Error, 'No type registered for book');
1530+
done();
1531+
});
1532+
1533+
it('should return deserialized data for a single data', function(done) {
1534+
const singleData = {
1535+
data: {
1536+
id: '1',
1537+
type: 'article',
1538+
attributes: {
1539+
title: 'JSON API paints my bikeshed!',
1540+
}
1541+
}
1542+
};
1543+
const deserializedData = Serializer.deserialize(typeOption, singleData);
1544+
1545+
expect(deserializedData).to.have.property('id').to.eql('1');
1546+
expect(deserializedData).to.have.property('title').to.eql('JSON API paints my bikeshed!');
1547+
done();
1548+
});
1549+
1550+
it('should return deserialized data for an array with mixed data', function(done) {
1551+
const arrayData = {
1552+
data: [{
1553+
id: '1',
1554+
type: 'article',
1555+
attributes: {
1556+
title: 'JSON API paints my bikeshed!',
1557+
}
1558+
}, {
1559+
id: '1',
1560+
type: 'people',
1561+
attributes: {
1562+
firstName: 'Kaley',
1563+
}
1564+
}]
1565+
};
1566+
const deserializedData = Serializer.deserialize(typeOption, arrayData);
1567+
expect(deserializedData).to.be.instanceof(Array).to.have.lengthOf(2);
1568+
expect(deserializedData[0]).to.have.property('id').to.eql('1');
1569+
expect(deserializedData[0]).to.have.property('title').to.eql('JSON API paints my bikeshed!');
1570+
expect(deserializedData[1]).to.have.property('id').to.eql('1');
1571+
expect(deserializedData[1]).to.have.property('firstName').to.eql('Kaley');
1572+
done();
1573+
});
1574+
1575+
it('should return deserialized data with a type resolved from a function deriving a type-string from data', function(done) {
1576+
const data = {
1577+
data: {
1578+
id: '1',
1579+
type: 'article',
1580+
attributes: {
1581+
title: 'JSON API paints my bikeshed!',
1582+
}
1583+
}
1584+
};
1585+
const typeFuncOption = {type: (data) => data.type ? 'article' : ''};
1586+
const deserializedData = Serializer.deserialize(typeOption, data);
1587+
1588+
expect(deserializedData).to.have.property('id').to.eql('1');
1589+
expect(deserializedData).to.have.property('title').to.eql('JSON API paints my bikeshed!');
1590+
1591+
done();
1592+
});
1593+
});
14991594
});

0 commit comments

Comments
 (0)