UNPKG

8.83 kBJavaScriptView Raw
1'use strict';
2var isPlainObject = require('lodash/isPlainObject');
3var isFunction = require('lodash/isFunction');
4var _find = require('lodash/find');
5var _merge = require('lodash/merge');
6var _identity = require('lodash/identity');
7var _transform = require('lodash/transform');
8var _mapValues = require('lodash/mapValues');
9var _mapKeys = require('lodash/mapKeys');
10var _pick = require('lodash/pick');
11var _pickBy = require('lodash/pickBy');
12var _keys = require('lodash/keys');
13var _each = require('lodash/each');
14var _isNil = require('lodash/isNil');
15var Inflector = require('./inflector');
16
17module.exports = function (collectionName, record, payload, opts) {
18 function isComplexType(obj) {
19 return Array.isArray(obj) || isPlainObject(obj);
20 }
21
22 function keyForAttribute(attribute) {
23 if (isPlainObject(attribute)) {
24 return _transform(attribute, function (result, value, key) {
25 if (isComplexType(value)) {
26 result[keyForAttribute(key)] = keyForAttribute(value);
27 } else {
28 result[keyForAttribute(key)] = value;
29 }
30 });
31 } else if (Array.isArray(attribute)) {
32 return attribute.map(function (attr) {
33 if (isComplexType(attr)) {
34 return keyForAttribute(attr);
35 } else {
36 return attr;
37 }
38 });
39 } else {
40 if (isFunction(opts.keyForAttribute)) {
41 return opts.keyForAttribute(attribute);
42 } else {
43 return Inflector.caserize(attribute, opts);
44 }
45 }
46 }
47
48 function getId() {
49 return opts.id || 'id';
50 }
51
52 function getRef(current, item, opts) {
53 if (isFunction(opts.ref)) {
54 return opts.ref(current, item);
55 } else if (opts.ref === true) {
56 if (Array.isArray(item)) {
57 return item.map(function (val) {
58 return String(val);
59 });
60 } else if (item) {
61 return String(item);
62 }
63 } else if (item && item[opts.ref]){
64 return String(item[opts.ref]);
65 }
66 }
67
68 function getType(str, attrVal) {
69 var type;
70 attrVal = attrVal || {};
71
72 if (isFunction(opts.typeForAttribute)) {
73 type = opts.typeForAttribute(str, attrVal);
74 }
75
76 // If the pluralize option is on, typeForAttribute returned undefined or wasn't used
77 if ((opts.pluralizeType === undefined || opts.pluralizeType) && type === undefined) {
78 type = Inflector.pluralize(str);
79 }
80
81 if (type === undefined) {
82 type = str;
83 }
84
85 return type;
86 }
87
88 function getLinks(current, links, dest) {
89 return _mapValues(links, function (value) {
90 if (isFunction(value)) {
91 return value(record, current, dest);
92 } else {
93 return value;
94 }
95 });
96 }
97
98 function getMeta(current, meta) {
99 if (isFunction(meta)) {
100 return meta(record);
101 } else {
102 return _mapValues(meta, function (value) {
103 if (isFunction(value)) {
104 return value(record, current);
105 } else {
106 return value;
107 }
108 });
109 }
110 }
111
112 function pick(obj, attributes) {
113 return _mapKeys(_pick(obj, attributes), function (value, key) {
114 return keyForAttribute(key);
115 });
116 }
117
118 function isCompoundDocumentIncluded(included, item) {
119 return _find(payload.included, { id: item.id, type: item.type });
120 }
121
122 function pushToIncluded(dest, include) {
123 var included = isCompoundDocumentIncluded(dest, include);
124 if (included) {
125 // Merge relationships
126 included.relationships = _merge(included.relationships,
127 _pickBy(include.relationships, _identity));
128
129 // Merge attributes
130 included.attributes = _merge(included.attributes,
131 _pickBy(include.attributes, _identity));
132 } else {
133 if (!dest.included) { dest.included = []; }
134 dest.included.push(include);
135 }
136 }
137
138 this.serialize = function (dest, current, attribute, opts) {
139 var that = this;
140 var data = null;
141
142 if (opts && opts.ref) {
143 if (!dest.relationships) { dest.relationships = {}; }
144
145 if (Array.isArray(current[attribute])) {
146 data = current[attribute].map(function (item) {
147 return that.serializeRef(item, current, attribute, opts);
148 });
149 } else {
150 data = that.serializeRef(current[attribute], current, attribute,
151 opts);
152 }
153
154 dest.relationships[keyForAttribute(attribute)] = {};
155 if (!opts.ignoreRelationshipData) {
156 dest.relationships[keyForAttribute(attribute)].data = data;
157 }
158
159 if (opts.relationshipLinks) {
160 var links = getLinks(current[attribute], opts.relationshipLinks, dest);
161 if (links.related) {
162 dest.relationships[keyForAttribute(attribute)].links = links;
163 }
164 }
165
166 if (opts.relationshipMeta) {
167 dest.relationships[keyForAttribute(attribute)].meta =
168 getMeta(current[attribute], opts.relationshipMeta);
169 }
170 } else {
171 if (Array.isArray(current[attribute])) {
172 if (current[attribute].length && isPlainObject(current[attribute][0])) {
173 data = current[attribute].map(function (item) {
174 return that.serializeNested(item, current, attribute, opts);
175 });
176 } else {
177 data = current[attribute];
178 }
179
180 dest.attributes[keyForAttribute(attribute)] = data;
181 } else if (isPlainObject(current[attribute])) {
182 data = that.serializeNested(current[attribute], current, attribute, opts);
183 dest.attributes[keyForAttribute(attribute)] = data;
184 } else {
185 dest.attributes[keyForAttribute(attribute)] = current[attribute];
186 }
187 }
188 };
189
190 this.serializeRef = function (dest, current, attribute, opts) {
191 var that = this;
192 var id = getRef(current, dest, opts);
193 var type = getType(attribute, dest);
194
195 var relationships = [];
196 var includedAttrs = [];
197
198 if (opts.attributes) {
199 if (dest) {
200 opts.attributes.forEach(function (attr) {
201 if (opts[attr] && !dest[attr] && opts[attr].nullIfMissing) {
202 dest[attr] = null;
203 }
204 });
205 }
206 relationships = opts.attributes.filter(function (attr) {
207 return opts[attr];
208 });
209
210 includedAttrs = opts.attributes.filter(function (attr) {
211 return !opts[attr];
212 });
213 }
214
215 var included = { type: type, id: id };
216 if (includedAttrs) { included.attributes = pick(dest, includedAttrs); }
217
218 relationships.forEach(function (relationship) {
219 if (dest && (isComplexType(dest[relationship]) || dest[relationship] === null)) {
220 that.serialize(included, dest, relationship, opts[relationship]);
221 }
222 });
223
224 if (includedAttrs.length &&
225 (opts.included === undefined || opts.included)) {
226 if (opts.includedLinks) {
227 included.links = getLinks(dest, opts.includedLinks);
228 }
229
230 if (typeof id !== 'undefined') { pushToIncluded(payload, included); }
231 }
232
233 return typeof id !== 'undefined' ? { type: type, id: id } : null;
234 };
235
236 this.serializeNested = function (dest, current, attribute, opts) {
237 var that = this;
238
239 var embeds = [];
240 var attributes = [];
241
242 if (opts && opts.attributes) {
243 embeds = opts.attributes.filter(function (attr) {
244 return opts[attr];
245 });
246
247 attributes = opts.attributes.filter(function (attr) {
248 return !opts[attr];
249 });
250 } else {
251 attributes = _keys(dest);
252 }
253
254 var ret = {};
255 if (attributes) { ret.attributes = pick(dest, attributes); }
256
257 embeds.forEach(function (embed) {
258 if (isComplexType(dest[embed])) {
259 that.serialize(ret, dest, embed, opts[embed]);
260 }
261 });
262
263 return ret.attributes;
264 };
265
266 this.perform = function () {
267 var that = this;
268
269 if( record === null ){
270 return null;
271 }
272
273 // If option is present, transform record
274 if (opts && opts.transform) {
275 record = opts.transform(record);
276 }
277
278 // Top-level data.
279 var data = { type: getType(collectionName, record) };
280 if (!_isNil(record[getId()])) { data.id = String(record[getId()]); }
281
282 // Data links.
283 if (opts.dataLinks) {
284 data.links = getLinks(record, opts.dataLinks);
285 }
286
287 // Data meta
288 if (opts.dataMeta) {
289 data.meta = getMeta(record, opts.dataMeta);
290 }
291
292 _each(opts.attributes, function (attribute) {
293 var splittedAttributes = attribute.split(':');
294
295 if (opts[attribute] && !record[attribute] && opts[attribute].nullIfMissing) {
296 record[attribute] = null;
297 }
298
299 if (splittedAttributes[0] in record) {
300 if (!data.attributes) { data.attributes = {}; }
301
302 var attributeMap = attribute;
303 if (splittedAttributes.length > 1) {
304 attribute = splittedAttributes[0];
305 attributeMap = splittedAttributes[1];
306 }
307
308 that.serialize(data, record, attribute, opts[attributeMap]);
309 }
310 });
311
312 return data;
313 };
314};