Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 49 additions & 33 deletions lib/JSONAPISerializer.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
'use strict';

const _ = require('lodash');
const _get = require('lodash/get');
const _set = require('lodash/set');
const _pick = require('lodash/pick');
const _difference = require('lodash/difference');
const _isPlainObject = require('lodash/isPlainObject');
const _find = require('lodash/find');
const _isEmpty = require('lodash/isEmpty');
const _uniqWith = require('lodash/uniqWith');
const _isEqual = require('lodash/isEqual');
const _isObjectLike = require('lodash/isObjectLike');
const _omitBy = require('lodash/omitBy');
const _isUndefined = require('lodash/isUndefined');
const _transform = require('lodash/transform');
const _snakeCase = require('lodash/snakeCase');
const _kebabCase = require('lodash/kebabCase');
const _camelCase = require('lodash/camelCase');

const joi = require('joi');
const intoStream = require('into-stream');
const through = require('through2');
Expand Down Expand Up @@ -107,7 +123,7 @@ module.exports = class JSONAPISerializer {
schema = schema || 'default';
options = Object.assign({}, this.opts, options);

_.set(this.schemas, [type, schema].join('.'), this.validateOptions(options));
_set(this.schemas, [type, schema].join('.'), this.validateOptions(options));
}

/**
Expand All @@ -125,7 +141,7 @@ module.exports = class JSONAPISerializer {
serialize(type, data, schema, extraData) {
// Support optional arguments (schema)
if (arguments.length === 3) {
if (_.isPlainObject(schema)) {
if (_isPlainObject(schema)) {
extraData = schema;
schema = 'default';
}
Expand All @@ -138,7 +154,7 @@ module.exports = class JSONAPISerializer {
let serializedData;
let options;

if (_.isPlainObject(type)) { // Serialize data with the dynamic type
if (_isPlainObject(type)) { // Serialize data with the dynamic type
options = this.validateDynamicTypeOptions(type);
// Override top level data
serializedData = this.serializeMixedData(options, data, included, extraData);
Expand Down Expand Up @@ -180,7 +196,7 @@ module.exports = class JSONAPISerializer {
serializeAsync(type, data, schema, extraData) {
// Support optional arguments (schema)
if (arguments.length === 3) {
if (_.isPlainObject(schema)) {
if (_isPlainObject(schema)) {
extraData = schema;
schema = 'default';
}
Expand All @@ -191,7 +207,7 @@ module.exports = class JSONAPISerializer {

const included = [];
const isDataArray = Array.isArray(data);
const isDynamicType = _.isPlainObject(type);
const isDynamicType = _isPlainObject(type);
let serializedData;
let serializedIncludes;
let options;
Expand Down Expand Up @@ -268,7 +284,7 @@ module.exports = class JSONAPISerializer {
deserialize(type, data, schema) {
schema = schema || 'default';

if (!_.isPlainObject(type)) {
if (!_isPlainObject(type)) {
if (!this.schemas[type]) {
throw new Error(`No type registered for ${type}`);
}
Expand Down Expand Up @@ -306,7 +322,7 @@ module.exports = class JSONAPISerializer {
deserializeAsync(type, data, schema) {
schema = schema || 'default';

if (!_.isPlainObject(type)) {
if (!_isPlainObject(type)) {
if (!this.schemas[type]) {
throw new Error(`No type registered for ${type}`);
}
Expand Down Expand Up @@ -348,8 +364,8 @@ module.exports = class JSONAPISerializer {
* @return {Object} deserialized data.
*/
deserializeResource(type, data, schema, included) {
if (_.isPlainObject(type)) {
type = (typeof type.type === 'function') ? type.type(data) : _.get(data, type.type);
if (_isPlainObject(type)) {
type = (typeof type.type === 'function') ? type.type(data) : _get(data, type.type);

if (!type) {
throw new Error(`No type can be resolved from data: ${JSON.stringify(data)}`);
Expand All @@ -370,12 +386,12 @@ module.exports = class JSONAPISerializer {

// whitelistOnDeserialize option
if (resourceOpts.whitelistOnDeserialize.length > 0) {
data.attributes = _.pick(data.attributes, resourceOpts.whitelistOnDeserialize);
data.attributes = _pick(data.attributes, resourceOpts.whitelistOnDeserialize);
}

// Remove unwanted keys (blacklistOnDeserialize option)
if (resourceOpts.blacklistOnDeserialize.length > 0) {
data.attributes = _.pick(data.attributes, _.difference(Object.keys(data.attributes), resourceOpts.blacklistOnDeserialize));
data.attributes = _pick(data.attributes, _difference(Object.keys(data.attributes), resourceOpts.blacklistOnDeserialize));
}

Object.assign(deserializedData, data.attributes);
Expand All @@ -394,15 +410,15 @@ module.exports = class JSONAPISerializer {
if (relationship.data !== undefined) {
if (Array.isArray(relationship.data)) {
// Array data
_.set(deserializedData, relationshipKey, relationship.data.map(d => (included
_set(deserializedData, relationshipKey, relationship.data.map(d => (included
? this.deserializeIncluded(d.type, d.id, resourceOpts.relationships[relationshipProperty], included)
: d.id)));
} else if (relationship.data === null) {
// null data
_.set(deserializedData, relationshipKey, null);
_set(deserializedData, relationshipKey, null);
} else {
// Object data
_.set(deserializedData, relationshipKey, included
_set(deserializedData, relationshipKey, included
? this.deserializeIncluded(relationship.data.type, relationship.data.id, resourceOpts.relationships[relationshipProperty], included)
: relationship.data.id);
}
Expand All @@ -426,7 +442,7 @@ module.exports = class JSONAPISerializer {
}

deserializeIncluded(type, id, relationshipOpts, included) {
const includedResource = _.find(included, {
const includedResource = _find(included, {
type,
id,
});
Expand All @@ -453,7 +469,7 @@ module.exports = class JSONAPISerializer {
*/
serializeData(type, data, options, included, extraData) {
// Empty data
if (_.isEmpty(data)) {
if (_isEmpty(data)) {
// Return [] or null
return Array.isArray(data) ? data : null;
}
Expand Down Expand Up @@ -487,7 +503,7 @@ module.exports = class JSONAPISerializer {
*/
serializeMixedData(typeOption, data, included, extraData) {
// Empty data
if (_.isEmpty(data)) {
if (_isEmpty(data)) {
// Return [] or null
return Array.isArray(data) ? data : null;
}
Expand All @@ -501,7 +517,7 @@ module.exports = class JSONAPISerializer {
// Resolve type from data (can be a string or a function deriving a type-string from each data-item)
const type = (typeof typeOption.type === 'function')
? typeOption.type(data)
: _.get(data, typeOption.type);
: _get(data, typeOption.type);

if (!type) {
throw new Error(`No type can be resolved from data: ${JSON.stringify(data)}`);
Expand All @@ -524,7 +540,7 @@ module.exports = class JSONAPISerializer {
* @return {Object[]} included.
*/
serializeIncluded(included) {
const serializedIncluded = _.uniqWith(included, _.isEqual);
const serializedIncluded = _uniqWith(included, _isEqual);
return Object.keys(serializedIncluded).length > 0 ? serializedIncluded : undefined;
}

Expand Down Expand Up @@ -565,7 +581,7 @@ module.exports = class JSONAPISerializer {
*/
serializeAttributes(data, options) {
if (options.whitelist.length > 0) {
data = _.pick(data, options.whitelist);
data = _pick(data, options.whitelist);
}

// Support alternativeKey options for relationships
Expand All @@ -578,7 +594,7 @@ module.exports = class JSONAPISerializer {
});

// Remove unwanted keys (id, blacklist, relationships, alternativeKeys)
let serializedAttributes = _.pick(data, _.difference(Object.keys(data), [options.id].concat(Object.keys(options.relationships), alternativeKeys, options.blacklist)));
let serializedAttributes = _pick(data, _difference(Object.keys(data), [options.id].concat(Object.keys(options.relationships), alternativeKeys, options.blacklist)));

if (options.convertCase) {
serializedAttributes = this._convertCase(serializedAttributes, options.convertCase);
Expand Down Expand Up @@ -614,7 +630,7 @@ module.exports = class JSONAPISerializer {
let serializeRelationship = {
links: this.processOptionsValues(data, extraData, rOptions.links),
meta: this.processOptionsValues(data, extraData, rOptions.meta),
data: this.serializeRelationship(rOptions.type, rOptions.schema, _.get(data, relationshipKey), included, data, extraData),
data: this.serializeRelationship(rOptions.type, rOptions.schema, _get(data, relationshipKey), included, data, extraData),
};

// Avoid empty relationship object
Expand All @@ -627,7 +643,7 @@ module.exports = class JSONAPISerializer {
// Convert case
relationship = (options.convertCase) ? this._convertCase(relationship, options.convertCase) : relationship;

_.set(serializedRelationships, relationship, serializeRelationship);
_set(serializedRelationships, relationship, serializeRelationship);
});

return Object.keys(serializedRelationships).length > 0 ? serializedRelationships : undefined;
Expand Down Expand Up @@ -656,7 +672,7 @@ module.exports = class JSONAPISerializer {
}

// Empty relationship data
if (!(typeof rData === 'number') && _.isEmpty(rData)) {
if (!(typeof rData === 'number') && _isEmpty(rData)) {
// Return [] or null
return Array.isArray(rData) ? rData : null;
}
Expand Down Expand Up @@ -686,7 +702,7 @@ module.exports = class JSONAPISerializer {
const serializedRelationship = { type };

// Support for unpopulated relationships (an id, or array of ids)
if (!_.isObjectLike(rData)) {
if (!_isObjectLike(rData)) {
serializedRelationship.id = rData.toString();
} else if (rData._bsontype && rData._bsontype === 'ObjectID') {
// Support for unpopulated relationships (with mongoDB BSON ObjectId)
Expand Down Expand Up @@ -731,7 +747,7 @@ module.exports = class JSONAPISerializer {
}

// Clean all undefined values
processedOptions = _.omitBy(processedOptions, _.isUndefined);
processedOptions = _omitBy(processedOptions, _isUndefined);

return Object.keys(processedOptions).length > 0 ? processedOptions : undefined;
}
Expand All @@ -747,9 +763,9 @@ module.exports = class JSONAPISerializer {
*/
_convertCase(data, convertCaseOptions) {
let converted;
if (Array.isArray(data) || _.isPlainObject(data)) {
converted = _.transform(data, (result, value, key) => {
if (Array.isArray(value) || _.isPlainObject(value)) {
if (Array.isArray(data) || _isPlainObject(data)) {
converted = _transform(data, (result, value, key) => {
if (Array.isArray(value) || _isPlainObject(value)) {
result[this._convertCase(key, convertCaseOptions)] = this._convertCase(value, convertCaseOptions);
} else {
result[this._convertCase(key, convertCaseOptions)] = value;
Expand All @@ -758,13 +774,13 @@ module.exports = class JSONAPISerializer {
} else {
switch (convertCaseOptions) {
case 'snake_case':
converted = _.snakeCase(data);
converted = _snakeCase(data);
break;
case 'kebab-case':
converted = _.kebabCase(data);
converted = _kebabCase(data);
break;
case 'camelCase':
converted = _.camelCase(data);
converted = _camelCase(data);
break;
default: // Do nothing
}
Expand Down