jsonapi-serializer
Version:
A Node.js framework agnostic library for serializing your data to JSON API
266 lines (221 loc) • 7.08 kB
JavaScript
'use strict';
var _ = require('lodash');
var Inflector = require('./inflector');
module.exports = function (collectionName, record, payload, opts) {
function keyForAttribute(attribute) {
if (_.isPlainObject(attribute)) {
return _.transform(attribute, function (result, value, key) {
if (isComplexType(value)) {
result[keyForAttribute(key)] = keyForAttribute(value);
} else {
result[keyForAttribute(key)] = value;
}
});
} else if (_.isArray(attribute)) {
return attribute.map(function (attr) {
if (isComplexType(attr)) {
return keyForAttribute(attr);
} else {
return attr;
}
});
} else {
if (_.isFunction(opts.keyForAttribute)) {
return opts.keyForAttribute(attribute);
} else {
return Inflector.caserize(attribute, opts);
}
}
}
function getId() {
return opts.id || 'id';
}
function getRef(current, item, opts) {
if (_.isFunction(opts.ref)) {
return opts.ref(current, item);
} else if (opts.ref === true) {
if (_.isArray(item)) {
return item.map(function (val) {
return String(val);
});
} else {
return String(item);
}
} else {
return String(item[opts.ref]);
}
}
function getType(str, attrVal) {
var type = str;
attrVal = attrVal || {};
if (_.isFunction(opts.typeForAttribute)) {
type = opts.typeForAttribute(str, attrVal);
} else if (_.isUndefined(opts.pluralizeType) || opts.pluralizeType) {
type = Inflector.pluralize(type);
}
return type;
}
function getLinks(current, links, dest) {
return _.mapValues(links, function (value) {
if (_.isFunction(value)) {
return value(record, current, dest);
} else {
return value;
}
});
}
function getMeta(current, meta) {
return _.mapValues(meta, function (value) {
if (_.isFunction(value)) {
return value(record, current);
} else {
return value;
}
});
}
function pick(obj, attributes) {
return _.mapKeys(_.pick(obj, attributes), function (value, key) {
return keyForAttribute(key);
});
}
function isComplexType(obj) {
return _.isArray(obj) || _.isPlainObject(obj);
}
function isCompoundDocumentIncluded(included, item) {
return _.find(payload.included, { id: item.id, type: item.type });
}
function pushToIncluded(dest, include) {
if (!isCompoundDocumentIncluded(dest, include)) {
if (!dest.included) { dest.included = []; }
dest.included.push(include);
}
}
this.serialize = function (dest, current, attribute, opts) {
var that = this;
var data = null;
if (!dest.relationships) { dest.relationships = {}; }
if (opts && opts.ref) {
var data = null;
if (_.isArray(current[attribute])) {
data = current[attribute].map(function (item) {
return that.serializeRef(item, current, attribute, opts);
});
} else {
data = that.serializeRef(current[attribute], current, attribute,
opts);
}
dest.relationships[keyForAttribute(attribute)] = {};
if (!opts.ignoreRelationshipData) {
dest.relationships[keyForAttribute(attribute)].data = data;
}
if (opts.relationshipLinks) {
dest.relationships[keyForAttribute(attribute)].links =
getLinks(current[attribute], opts.relationshipLinks, dest);
}
if (opts.relationshipMeta) {
dest.relationships[keyForAttribute(attribute)].meta =
getMeta(current[attribute], opts.relationshipMeta);
}
} else {
var data = null;
if (_.isArray(current[attribute])) {
if (current[attribute].length && _.isPlainObject(current[attribute][0])) {
data = current[attribute].map(function (item) {
return that.serializeNested(item, current, attribute, opts);
});
} else {
data = current[attribute];
}
dest.attributes[keyForAttribute(attribute)] = data;
} else if (_.isPlainObject(current[attribute])) {
data = that.serializeNested(current[attribute], current, attribute, opts);
dest.attributes[keyForAttribute(attribute)] = data;
} else {
dest.attributes[keyForAttribute(attribute)] = current[attribute];
}
}
};
this.serializeRef = function (dest, current, attribute, opts) {
var that = this;
var id = getRef(current, dest, opts);
var type = getType(attribute, dest);
var relationships = [];
var includedAttrs = [];
if (opts.attributes) {
relationships = opts.attributes.filter(function (attr) {
return opts[attr];
});
includedAttrs = opts.attributes.filter(function (attr) {
return !opts[attr];
});
}
var included = { type: type, id: id };
if (includedAttrs) { included.attributes = pick(dest, includedAttrs); }
relationships.forEach(function (relationship) {
if (isComplexType(dest[relationship])) {
that.serialize(included, dest, relationship, opts[relationship]);
}
});
if (includedAttrs.length &&
(_.isUndefined(opts.included) || opts.included)) {
if (opts.includedLinks) {
included.links = getLinks(dest, opts.includedLinks);
}
pushToIncluded(payload, included);
}
return id !== 'undefined' ? { type: type, id: id } : null;
};
this.serializeNested = function (dest, current, attribute, opts) {
var that = this;
var embeds = [];
var attributes = [];
if (opts && opts.attributes) {
embeds = opts.attributes.filter(function (attr) {
return opts[attr];
});
attributes = opts.attributes.filter(function (attr) {
return !opts[attr];
});
} else {
attributes = _.keys(dest);
}
var ret = {};
if (attributes) { ret.attributes = pick(dest, attributes); }
embeds.forEach(function (embed) {
if (isComplexType(dest[embed])) {
that.serialize(ret, dest, embed, opts[embed]);
}
});
return ret.attributes;
};
this.perform = function () {
var that = this;
if( _.isNull( record ) ){
return null;
}
// Top-level data.
var data = {
type: getType(collectionName, record),
id: String(record[getId()])
};
// Data links.
if (opts.dataLinks) {
data.links = getLinks(record, opts.dataLinks);
}
_.each(opts.attributes, function (attribute) {
var splittedAttributes = attribute.split(':');
if (splittedAttributes[0] in record) {
if (!data.attributes) { data.attributes = {}; }
var attributeMap = attribute;
if (splittedAttributes.length > 1) {
attribute = splittedAttributes[0];
attributeMap = splittedAttributes[1];
}
that.serialize(data, record, attribute, opts[attributeMap]);
} else {
return;
}
});
return data;
};
};