@ember-data-mirror/serializer 5.4.0-alpha.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE.md +11 -0
  2. package/README.md +98 -0
  3. package/addon/-private.js +3 -0
  4. package/addon/-private.js.map +1 -0
  5. package/addon/embedded-records-mixin-QJe_8jrF.js +572 -0
  6. package/addon/embedded-records-mixin-QJe_8jrF.js.map +1 -0
  7. package/addon/index.js +181 -0
  8. package/addon/index.js.map +1 -0
  9. package/addon/json-api.js +508 -0
  10. package/addon/json-api.js.map +1 -0
  11. package/addon/json.js +1375 -0
  12. package/addon/json.js.map +1 -0
  13. package/addon/rest.js +684 -0
  14. package/addon/rest.js.map +1 -0
  15. package/addon/string-Juwz4cu0.js +345 -0
  16. package/addon/string-Juwz4cu0.js.map +1 -0
  17. package/addon/transform.js +1 -0
  18. package/addon/transform.js.map +1 -0
  19. package/addon/utils-NcVD2Jb5.js +12 -0
  20. package/addon/utils-NcVD2Jb5.js.map +1 -0
  21. package/addon-main.js +94 -0
  22. package/blueprints/serializer/files/__root__/__path__/__name__.js +4 -0
  23. package/blueprints/serializer/index.js +14 -0
  24. package/blueprints/serializer/native-files/__root__/__path__/__name__.js +4 -0
  25. package/blueprints/serializer-test/index.js +29 -0
  26. package/blueprints/serializer-test/qunit-files/__root__/__path__/__test__.js +24 -0
  27. package/blueprints/transform/files/__root__/__path__/__name__.js +13 -0
  28. package/blueprints/transform/index.js +7 -0
  29. package/blueprints/transform/native-files/__root__/__path__/__name__.js +13 -0
  30. package/blueprints/transform-test/index.js +29 -0
  31. package/blueprints/transform-test/qunit-files/__root__/__path__/__test__.js +13 -0
  32. package/package.json +113 -0
  33. package/unstable-preview-types/-private/embedded-records-mixin.d.ts +7 -0
  34. package/unstable-preview-types/-private/embedded-records-mixin.d.ts.map +1 -0
  35. package/unstable-preview-types/-private/transforms/boolean.d.ts +52 -0
  36. package/unstable-preview-types/-private/transforms/boolean.d.ts.map +1 -0
  37. package/unstable-preview-types/-private/transforms/date.d.ts +33 -0
  38. package/unstable-preview-types/-private/transforms/date.d.ts.map +1 -0
  39. package/unstable-preview-types/-private/transforms/number.d.ts +34 -0
  40. package/unstable-preview-types/-private/transforms/number.d.ts.map +1 -0
  41. package/unstable-preview-types/-private/transforms/string.d.ts +34 -0
  42. package/unstable-preview-types/-private/transforms/string.d.ts.map +1 -0
  43. package/unstable-preview-types/-private/transforms/transform.d.ts +128 -0
  44. package/unstable-preview-types/-private/transforms/transform.d.ts.map +1 -0
  45. package/unstable-preview-types/-private/utils.d.ts +6 -0
  46. package/unstable-preview-types/-private/utils.d.ts.map +1 -0
  47. package/unstable-preview-types/-private.d.ts +13 -0
  48. package/unstable-preview-types/-private.d.ts.map +1 -0
  49. package/unstable-preview-types/index.d.ts +278 -0
  50. package/unstable-preview-types/index.d.ts.map +1 -0
  51. package/unstable-preview-types/json-api.d.ts +515 -0
  52. package/unstable-preview-types/json-api.d.ts.map +1 -0
  53. package/unstable-preview-types/json.d.ts +1094 -0
  54. package/unstable-preview-types/json.d.ts.map +1 -0
  55. package/unstable-preview-types/rest.d.ts +602 -0
  56. package/unstable-preview-types/rest.d.ts.map +1 -0
  57. package/unstable-preview-types/transform.d.ts +7 -0
  58. package/unstable-preview-types/transform.d.ts.map +1 -0
package/addon/json.js ADDED
@@ -0,0 +1,1375 @@
1
+ import { getOwner } from '@ember/application';
2
+ import { assert, warn } from '@ember/debug';
3
+ import { dasherize } from '@ember/string';
4
+ import { singularize } from 'ember-inflector';
5
+ import _class from "./index";
6
+ import { c as coerceId } from "./utils-NcVD2Jb5";
7
+
8
+ /**
9
+ * @module @ember-data-mirror/serializer/json
10
+ */
11
+ const SOURCE_POINTER_REGEXP = /^\/?data\/(attributes|relationships)\/(.*)/;
12
+ const SOURCE_POINTER_PRIMARY_REGEXP = /^\/?data/;
13
+ const PRIMARY_ATTRIBUTE_KEY = 'base';
14
+
15
+ /**
16
+ * <blockquote style="margin: 1em; padding: .1em 1em .1em 1em; border-left: solid 1em #E34C32; background: #e0e0e0;">
17
+ <p>
18
+ ⚠️ <strong>This is LEGACY documentation</strong> for a feature that is no longer encouraged to be used.
19
+ If starting a new app or thinking of implementing a new adapter, consider writing a
20
+ <a href="/ember-data/release/classes/%3CInterface%3E%20Handler">Handler</a> instead to be used with the <a href="https://github.com/emberjs/data/tree/main/packages/request#readme">RequestManager</a>
21
+ </p>
22
+ </blockquote>
23
+
24
+ In EmberData a Serializer is used to serialize and deserialize
25
+ records when they are transferred in and out of an external source.
26
+ This process involves normalizing property names, transforming
27
+ attribute values and serializing relationships.
28
+
29
+ By default, EmberData uses and recommends the `JSONAPISerializer`.
30
+
31
+ `JSONSerializer` is useful for simpler or legacy backends that may
32
+ not support the http://jsonapi.org/ spec.
33
+
34
+ For example, given the following `User` model and JSON payload:
35
+
36
+ ```app/models/user.js
37
+ import Model, { attr, belongsTo, hasMany } from '@ember-data-mirror/model';
38
+
39
+ export default class UserModel extends Model {
40
+ @hasMany('user') friends;
41
+ @belongsTo('location') house;
42
+
43
+ @attr('string') name;
44
+ }
45
+ ```
46
+
47
+ ```js
48
+ {
49
+ id: 1,
50
+ name: 'Sebastian',
51
+ friends: [3, 4],
52
+ links: {
53
+ house: '/houses/lefkada'
54
+ }
55
+ }
56
+ ```
57
+
58
+ `JSONSerializer` will normalize the JSON payload to the JSON API format that the
59
+ Ember Data store expects.
60
+
61
+ You can customize how JSONSerializer processes its payload by passing options in
62
+ the `attrs` hash or by subclassing the `JSONSerializer` and overriding hooks:
63
+
64
+ - To customize how a single record is normalized, use the `normalize` hook.
65
+ - To customize how `JSONSerializer` normalizes the whole server response, use the
66
+ `normalizeResponse` hook.
67
+ - To customize how `JSONSerializer` normalizes a specific response from the server,
68
+ use one of the many specific `normalizeResponse` hooks.
69
+ - To customize how `JSONSerializer` normalizes your id, attributes or relationships,
70
+ use the `extractId`, `extractAttributes` and `extractRelationships` hooks.
71
+
72
+ The `JSONSerializer` normalization process follows these steps:
73
+
74
+ 1. `normalizeResponse`
75
+ - entry method to the serializer.
76
+ 2. `normalizeCreateRecordResponse`
77
+ - a `normalizeResponse` for a specific operation is called.
78
+ 3. `normalizeSingleResponse`|`normalizeArrayResponse`
79
+ - for methods like `createRecord` we expect a single record back, while for methods like `findAll` we expect multiple records back.
80
+ 4. `normalize`
81
+ - `normalizeArrayResponse` iterates and calls `normalize` for each of its records while `normalizeSingle`
82
+ calls it once. This is the method you most likely want to subclass.
83
+ 5. `extractId` | `extractAttributes` | `extractRelationships`
84
+ - `normalize` delegates to these methods to
85
+ turn the record payload into the JSON API format.
86
+
87
+ @main @ember-data-mirror/serializer/json
88
+ @class JSONSerializer
89
+ @public
90
+ @extends Serializer
91
+ */
92
+ const JSONSerializer = _class.extend({
93
+ /**
94
+ The `primaryKey` is used when serializing and deserializing
95
+ data. Ember Data always uses the `id` property to store the id of
96
+ the record. The external source may not always follow this
97
+ convention. In these cases it is useful to override the
98
+ `primaryKey` property to match the `primaryKey` of your external
99
+ store.
100
+ Example
101
+ ```app/serializers/application.js
102
+ import JSONSerializer from '@ember-data-mirror/serializer/json';
103
+ export default class ApplicationSerializer extends JSONSerializer {
104
+ primaryKey = '_id'
105
+ }
106
+ ```
107
+ @property primaryKey
108
+ @type {String}
109
+ @public
110
+ @default 'id'
111
+ */
112
+ primaryKey: 'id',
113
+ /**
114
+ The `attrs` object can be used to declare a simple mapping between
115
+ property names on `Model` records and payload keys in the
116
+ serialized JSON object representing the record. An object with the
117
+ property `key` can also be used to designate the attribute's key on
118
+ the response payload.
119
+ Example
120
+ ```app/models/person.js
121
+ import Model, { attr } from '@ember-data-mirror/model';
122
+ export default class PersonModel extends Model {
123
+ @attr('string') firstName;
124
+ @attr('string') lastName;
125
+ @attr('string') occupation;
126
+ @attr('boolean') admin;
127
+ }
128
+ ```
129
+ ```app/serializers/person.js
130
+ import JSONSerializer from '@ember-data-mirror/serializer/json';
131
+ export default class PersonSerializer extends JSONSerializer {
132
+ attrs = {
133
+ admin: 'is_admin',
134
+ occupation: { key: 'career' }
135
+ }
136
+ }
137
+ ```
138
+ You can also remove attributes and relationships by setting the `serialize`
139
+ key to `false` in your mapping object.
140
+ Example
141
+ ```app/serializers/person.js
142
+ import JSONSerializer from '@ember-data-mirror/serializer/json';
143
+ export default class PostSerializer extends JSONSerializer {
144
+ attrs = {
145
+ admin: { serialize: false },
146
+ occupation: { key: 'career' }
147
+ }
148
+ }
149
+ ```
150
+ When serialized:
151
+ ```javascript
152
+ {
153
+ "firstName": "Harry",
154
+ "lastName": "Houdini",
155
+ "career": "magician"
156
+ }
157
+ ```
158
+ Note that the `admin` is now not included in the payload.
159
+ Setting `serialize` to `true` enforces serialization for hasMany
160
+ relationships even if it's neither a many-to-many nor many-to-none
161
+ relationship.
162
+ @property attrs
163
+ @public
164
+ @type {Object}
165
+ */
166
+ mergedProperties: ['attrs'],
167
+ /**
168
+ Given a subclass of `Model` and a JSON object this method will
169
+ iterate through each attribute of the `Model` and invoke the
170
+ `Transform#deserialize` method on the matching property of the
171
+ JSON object. This method is typically called after the
172
+ serializer's `normalize` method.
173
+ @method applyTransforms
174
+ @private
175
+ @param {Model} typeClass
176
+ @param {Object} data The data to transform
177
+ @return {Object} data The transformed data object
178
+ */
179
+ applyTransforms(typeClass, data) {
180
+ const attributes = typeClass.attributes;
181
+ typeClass.eachTransformedAttribute((key, typeClass) => {
182
+ if (data[key] === undefined) {
183
+ return;
184
+ }
185
+ const transform = this.transformFor(typeClass);
186
+ const transformMeta = attributes.get(key);
187
+ data[key] = transform.deserialize(data[key], transformMeta.options);
188
+ });
189
+ return data;
190
+ },
191
+ /**
192
+ The `normalizeResponse` method is used to normalize a payload from the
193
+ server to a JSON-API Document.
194
+ http://jsonapi.org/format/#document-structure
195
+ This method delegates to a more specific normalize method based on
196
+ the `requestType`.
197
+ To override this method with a custom one, make sure to call
198
+ `return super.normalizeResponse(store, primaryModelClass, payload, id, requestType)` with your
199
+ pre-processed data.
200
+ Here's an example of using `normalizeResponse` manually:
201
+ ```javascript
202
+ socket.on('message', function(message) {
203
+ let data = message.data;
204
+ let modelClass = store.modelFor(data.modelName);
205
+ let serializer = store.serializerFor(data.modelName);
206
+ let normalized = serializer.normalizeSingleResponse(store, modelClass, data, data.id);
207
+ store.push(normalized);
208
+ });
209
+ ```
210
+ @since 1.13.0
211
+ @method normalizeResponse
212
+ @public
213
+ @param {Store} store
214
+ @param {Model} primaryModelClass
215
+ @param {Object} payload
216
+ @param {String|Number} id
217
+ @param {String} requestType
218
+ @return {Object} JSON-API Document
219
+ */
220
+ normalizeResponse(store, primaryModelClass, payload, id, requestType) {
221
+ switch (requestType) {
222
+ case 'findRecord':
223
+ return this.normalizeFindRecordResponse(...arguments);
224
+ case 'queryRecord':
225
+ return this.normalizeQueryRecordResponse(...arguments);
226
+ case 'findAll':
227
+ return this.normalizeFindAllResponse(...arguments);
228
+ case 'findBelongsTo':
229
+ return this.normalizeFindBelongsToResponse(...arguments);
230
+ case 'findHasMany':
231
+ return this.normalizeFindHasManyResponse(...arguments);
232
+ case 'findMany':
233
+ return this.normalizeFindManyResponse(...arguments);
234
+ case 'query':
235
+ return this.normalizeQueryResponse(...arguments);
236
+ case 'createRecord':
237
+ return this.normalizeCreateRecordResponse(...arguments);
238
+ case 'deleteRecord':
239
+ return this.normalizeDeleteRecordResponse(...arguments);
240
+ case 'updateRecord':
241
+ return this.normalizeUpdateRecordResponse(...arguments);
242
+ }
243
+ },
244
+ /**
245
+ Called by the default normalizeResponse implementation when the
246
+ type of request is `findRecord`
247
+ @since 1.13.0
248
+ @method normalizeFindRecordResponse
249
+ @public
250
+ @param {Store} store
251
+ @param {Model} primaryModelClass
252
+ @param {Object} payload
253
+ @param {String|Number} id
254
+ @param {String} requestType
255
+ @return {Object} JSON-API Document
256
+ */
257
+ normalizeFindRecordResponse(store, primaryModelClass, payload, id, requestType) {
258
+ return this.normalizeSingleResponse(...arguments);
259
+ },
260
+ /**
261
+ Called by the default normalizeResponse implementation when the
262
+ type of request is `queryRecord`
263
+ @since 1.13.0
264
+ @method normalizeQueryRecordResponse
265
+ @public
266
+ @param {Store} store
267
+ @param {Model} primaryModelClass
268
+ @param {Object} payload
269
+ @param {String|Number} id
270
+ @param {String} requestType
271
+ @return {Object} JSON-API Document
272
+ */
273
+ normalizeQueryRecordResponse(store, primaryModelClass, payload, id, requestType) {
274
+ return this.normalizeSingleResponse(...arguments);
275
+ },
276
+ /**
277
+ Called by the default normalizeResponse implementation when the
278
+ type of request is `findAll`
279
+ @since 1.13.0
280
+ @method normalizeFindAllResponse
281
+ @public
282
+ @param {Store} store
283
+ @param {Model} primaryModelClass
284
+ @param {Object} payload
285
+ @param {String|Number} id
286
+ @param {String} requestType
287
+ @return {Object} JSON-API Document
288
+ */
289
+ normalizeFindAllResponse(store, primaryModelClass, payload, id, requestType) {
290
+ return this.normalizeArrayResponse(...arguments);
291
+ },
292
+ /**
293
+ Called by the default normalizeResponse implementation when the
294
+ type of request is `findBelongsTo`
295
+ @since 1.13.0
296
+ @method normalizeFindBelongsToResponse
297
+ @public
298
+ @param {Store} store
299
+ @param {Model} primaryModelClass
300
+ @param {Object} payload
301
+ @param {String|Number} id
302
+ @param {String} requestType
303
+ @return {Object} JSON-API Document
304
+ */
305
+ normalizeFindBelongsToResponse(store, primaryModelClass, payload, id, requestType) {
306
+ return this.normalizeSingleResponse(...arguments);
307
+ },
308
+ /**
309
+ Called by the default normalizeResponse implementation when the
310
+ type of request is `findHasMany`
311
+ @since 1.13.0
312
+ @method normalizeFindHasManyResponse
313
+ @public
314
+ @param {Store} store
315
+ @param {Model} primaryModelClass
316
+ @param {Object} payload
317
+ @param {String|Number} id
318
+ @param {String} requestType
319
+ @return {Object} JSON-API Document
320
+ */
321
+ normalizeFindHasManyResponse(store, primaryModelClass, payload, id, requestType) {
322
+ return this.normalizeArrayResponse(...arguments);
323
+ },
324
+ /**
325
+ Called by the default normalizeResponse implementation when the
326
+ type of request is `findMany`
327
+ @since 1.13.0
328
+ @method normalizeFindManyResponse
329
+ @public
330
+ @param {Store} store
331
+ @param {Model} primaryModelClass
332
+ @param {Object} payload
333
+ @param {String|Number} id
334
+ @param {String} requestType
335
+ @return {Object} JSON-API Document
336
+ */
337
+ normalizeFindManyResponse(store, primaryModelClass, payload, id, requestType) {
338
+ return this.normalizeArrayResponse(...arguments);
339
+ },
340
+ /**
341
+ Called by the default normalizeResponse implementation when the
342
+ type of request is `query`
343
+ @since 1.13.0
344
+ @method normalizeQueryResponse
345
+ @public
346
+ @param {Store} store
347
+ @param {Model} primaryModelClass
348
+ @param {Object} payload
349
+ @param {String|Number} id
350
+ @param {String} requestType
351
+ @return {Object} JSON-API Document
352
+ */
353
+ normalizeQueryResponse(store, primaryModelClass, payload, id, requestType) {
354
+ return this.normalizeArrayResponse(...arguments);
355
+ },
356
+ /**
357
+ Called by the default normalizeResponse implementation when the
358
+ type of request is `createRecord`
359
+ @since 1.13.0
360
+ @method normalizeCreateRecordResponse
361
+ @public
362
+ @param {Store} store
363
+ @param {Model} primaryModelClass
364
+ @param {Object} payload
365
+ @param {String|Number} id
366
+ @param {String} requestType
367
+ @return {Object} JSON-API Document
368
+ */
369
+ normalizeCreateRecordResponse(store, primaryModelClass, payload, id, requestType) {
370
+ return this.normalizeSaveResponse(...arguments);
371
+ },
372
+ /**
373
+ Called by the default normalizeResponse implementation when the
374
+ type of request is `deleteRecord`
375
+ @since 1.13.0
376
+ @method normalizeDeleteRecordResponse
377
+ @public
378
+ @param {Store} store
379
+ @param {Model} primaryModelClass
380
+ @param {Object} payload
381
+ @param {String|Number} id
382
+ @param {String} requestType
383
+ @return {Object} JSON-API Document
384
+ */
385
+ normalizeDeleteRecordResponse(store, primaryModelClass, payload, id, requestType) {
386
+ return this.normalizeSaveResponse(...arguments);
387
+ },
388
+ /**
389
+ Called by the default normalizeResponse implementation when the
390
+ type of request is `updateRecord`
391
+ @since 1.13.0
392
+ @method normalizeUpdateRecordResponse
393
+ @public
394
+ @param {Store} store
395
+ @param {Model} primaryModelClass
396
+ @param {Object} payload
397
+ @param {String|Number} id
398
+ @param {String} requestType
399
+ @return {Object} JSON-API Document
400
+ */
401
+ normalizeUpdateRecordResponse(store, primaryModelClass, payload, id, requestType) {
402
+ return this.normalizeSaveResponse(...arguments);
403
+ },
404
+ /**
405
+ normalizeUpdateRecordResponse, normalizeCreateRecordResponse and
406
+ normalizeDeleteRecordResponse delegate to this method by default.
407
+ @since 1.13.0
408
+ @method normalizeSaveResponse
409
+ @public
410
+ @param {Store} store
411
+ @param {Model} primaryModelClass
412
+ @param {Object} payload
413
+ @param {String|Number} id
414
+ @param {String} requestType
415
+ @return {Object} JSON-API Document
416
+ */
417
+ normalizeSaveResponse(store, primaryModelClass, payload, id, requestType) {
418
+ return this.normalizeSingleResponse(...arguments);
419
+ },
420
+ /**
421
+ normalizeQueryResponse and normalizeFindRecordResponse delegate to this
422
+ method by default.
423
+ @since 1.13.0
424
+ @method normalizeSingleResponse
425
+ @public
426
+ @param {Store} store
427
+ @param {Model} primaryModelClass
428
+ @param {Object} payload
429
+ @param {String|Number} id
430
+ @param {String} requestType
431
+ @return {Object} JSON-API Document
432
+ */
433
+ normalizeSingleResponse(store, primaryModelClass, payload, id, requestType) {
434
+ return this._normalizeResponse(store, primaryModelClass, payload, id, requestType, true);
435
+ },
436
+ /**
437
+ normalizeQueryResponse, normalizeFindManyResponse, and normalizeFindHasManyResponse delegate
438
+ to this method by default.
439
+ @since 1.13.0
440
+ @method normalizeArrayResponse
441
+ @public
442
+ @param {Store} store
443
+ @param {Model} primaryModelClass
444
+ @param {Object} payload
445
+ @param {String|Number} id
446
+ @param {String} requestType
447
+ @return {Object} JSON-API Document
448
+ */
449
+ normalizeArrayResponse(store, primaryModelClass, payload, id, requestType) {
450
+ return this._normalizeResponse(store, primaryModelClass, payload, id, requestType, false);
451
+ },
452
+ /**
453
+ @method _normalizeResponse
454
+ @param {Store} store
455
+ @param {Model} primaryModelClass
456
+ @param {Object} payload
457
+ @param {String|Number} id
458
+ @param {String} requestType
459
+ @param {Boolean} isSingle
460
+ @return {Object} JSON-API Document
461
+ @private
462
+ */
463
+ _normalizeResponse(store, primaryModelClass, payload, id, requestType, isSingle) {
464
+ const documentHash = {
465
+ data: null,
466
+ included: []
467
+ };
468
+ const meta = this.extractMeta(store, primaryModelClass, payload);
469
+ if (meta) {
470
+ assert('The `meta` returned from `extractMeta` has to be an object, not "' + typeof meta + '".', typeof meta === 'object');
471
+ documentHash.meta = meta;
472
+ }
473
+ if (isSingle) {
474
+ const {
475
+ data,
476
+ included
477
+ } = this.normalize(primaryModelClass, payload);
478
+ documentHash.data = data;
479
+ if (included) {
480
+ documentHash.included = included;
481
+ }
482
+ } else {
483
+ const ret = new Array(payload.length);
484
+ for (let i = 0, l = payload.length; i < l; i++) {
485
+ const item = payload[i];
486
+ const {
487
+ data,
488
+ included
489
+ } = this.normalize(primaryModelClass, item);
490
+ if (included) {
491
+ documentHash.included = documentHash.included.concat(included);
492
+ }
493
+ ret[i] = data;
494
+ }
495
+ documentHash.data = ret;
496
+ }
497
+ return documentHash;
498
+ },
499
+ /**
500
+ Normalizes a part of the JSON payload returned by
501
+ the server. You should override this method, munge the hash
502
+ and call super if you have generic normalization to do.
503
+ It takes the type of the record that is being normalized
504
+ (as a Model class), the property where the hash was
505
+ originally found, and the hash to normalize.
506
+ You can use this method, for example, to normalize underscored keys to camelized
507
+ or other general-purpose normalizations.
508
+ Example
509
+ ```app/serializers/application.js
510
+ import JSONSerializer from '@ember-data-mirror/serializer/json';
511
+ import { underscore } from '<app-name>/utils/string-utils';
512
+ import { get } from '@ember/object';
513
+ export default class ApplicationSerializer extends JSONSerializer {
514
+ normalize(typeClass, hash) {
515
+ let fields = typeClass.fields;
516
+ fields.forEach(function(type, field) {
517
+ let payloadField = underscore(field);
518
+ if (field === payloadField) { return; }
519
+ hash[field] = hash[payloadField];
520
+ delete hash[payloadField];
521
+ });
522
+ return super.normalize(...arguments);
523
+ }
524
+ }
525
+ ```
526
+ @method normalize
527
+ @public
528
+ @param {Model} typeClass
529
+ @param {Object} hash
530
+ @return {Object}
531
+ */
532
+ normalize(modelClass, resourceHash) {
533
+ let data = null;
534
+ if (resourceHash) {
535
+ this.normalizeUsingDeclaredMapping(modelClass, resourceHash);
536
+ if (typeof resourceHash.links === 'object') {
537
+ this.normalizeUsingDeclaredMapping(modelClass, resourceHash.links);
538
+ }
539
+ data = {
540
+ id: this.extractId(modelClass, resourceHash),
541
+ type: modelClass.modelName,
542
+ attributes: this.extractAttributes(modelClass, resourceHash),
543
+ relationships: this.extractRelationships(modelClass, resourceHash)
544
+ };
545
+ this.applyTransforms(modelClass, data.attributes);
546
+ }
547
+ return {
548
+ data
549
+ };
550
+ },
551
+ /**
552
+ Returns the resource's ID.
553
+ @method extractId
554
+ @public
555
+ @param {Object} modelClass
556
+ @param {Object} resourceHash
557
+ @return {String}
558
+ */
559
+ extractId(modelClass, resourceHash) {
560
+ const primaryKey = this.primaryKey;
561
+ const id = resourceHash[primaryKey];
562
+ return coerceId(id);
563
+ },
564
+ /**
565
+ Returns the resource's attributes formatted as a JSON-API "attributes object".
566
+ http://jsonapi.org/format/#document-resource-object-attributes
567
+ @method extractAttributes
568
+ @public
569
+ @param {Object} modelClass
570
+ @param {Object} resourceHash
571
+ @return {Object}
572
+ */
573
+ extractAttributes(modelClass, resourceHash) {
574
+ let attributeKey;
575
+ const attributes = {};
576
+ modelClass.eachAttribute(key => {
577
+ attributeKey = this.keyForAttribute(key, 'deserialize');
578
+ if (resourceHash[attributeKey] !== undefined) {
579
+ attributes[key] = resourceHash[attributeKey];
580
+ }
581
+ });
582
+ return attributes;
583
+ },
584
+ /**
585
+ Returns a relationship formatted as a JSON-API "relationship object".
586
+ http://jsonapi.org/format/#document-resource-object-relationships
587
+ @method extractRelationship
588
+ @public
589
+ @param {Object} relationshipModelName
590
+ @param {Object} relationshipHash
591
+ @return {Object}
592
+ */
593
+ extractRelationship(relationshipModelName, relationshipHash) {
594
+ if (!relationshipHash) {
595
+ return null;
596
+ }
597
+ /*
598
+ When `relationshipHash` is an object it usually means that the relationship
599
+ is polymorphic. It could however also be embedded resources that the
600
+ EmbeddedRecordsMixin has be able to process.
601
+ */
602
+ if (relationshipHash && typeof relationshipHash === 'object' && !Array.isArray(relationshipHash)) {
603
+ if (relationshipHash.id) {
604
+ relationshipHash.id = coerceId(relationshipHash.id);
605
+ }
606
+ const modelClass = this.store.modelFor(relationshipModelName);
607
+ if (relationshipHash.type && !modelClass.fields.has('type')) {
608
+ relationshipHash.type = this.modelNameFromPayloadKey(relationshipHash.type);
609
+ }
610
+ return relationshipHash;
611
+ }
612
+ return {
613
+ id: coerceId(relationshipHash),
614
+ type: dasherize(singularize(relationshipModelName))
615
+ };
616
+ },
617
+ /**
618
+ Returns a polymorphic relationship formatted as a JSON-API "relationship object".
619
+ http://jsonapi.org/format/#document-resource-object-relationships
620
+ `relationshipOptions` is a hash which contains more information about the
621
+ polymorphic relationship which should be extracted:
622
+ - `resourceHash` complete hash of the resource the relationship should be
623
+ extracted from
624
+ - `relationshipKey` key under which the value for the relationship is
625
+ extracted from the resourceHash
626
+ - `relationshipMeta` meta information about the relationship
627
+ @method extractPolymorphicRelationship
628
+ @public
629
+ @param {Object} relationshipModelName
630
+ @param {Object} relationshipHash
631
+ @param {Object} relationshipOptions
632
+ @return {Object}
633
+ */
634
+ extractPolymorphicRelationship(relationshipModelName, relationshipHash, relationshipOptions) {
635
+ return this.extractRelationship(relationshipModelName, relationshipHash);
636
+ },
637
+ /**
638
+ Returns the resource's relationships formatted as a JSON-API "relationships object".
639
+ http://jsonapi.org/format/#document-resource-object-relationships
640
+ @method extractRelationships
641
+ @public
642
+ @param {Object} modelClass
643
+ @param {Object} resourceHash
644
+ @return {Object}
645
+ */
646
+ extractRelationships(modelClass, resourceHash) {
647
+ const relationships = {};
648
+ modelClass.eachRelationship((key, relationshipMeta) => {
649
+ let relationship = null;
650
+ const relationshipKey = this.keyForRelationship(key, relationshipMeta.kind, 'deserialize');
651
+ if (resourceHash[relationshipKey] !== undefined) {
652
+ let data = null;
653
+ const relationshipHash = resourceHash[relationshipKey];
654
+ if (relationshipMeta.kind === 'belongsTo') {
655
+ if (relationshipMeta.options.polymorphic) {
656
+ // extracting a polymorphic belongsTo may need more information
657
+ // than the type and the hash (which might only be an id) for the
658
+ // relationship, hence we pass the key, resource and
659
+ // relationshipMeta too
660
+ data = this.extractPolymorphicRelationship(relationshipMeta.type, relationshipHash, {
661
+ key,
662
+ resourceHash,
663
+ relationshipMeta
664
+ });
665
+ } else {
666
+ data = this.extractRelationship(relationshipMeta.type, relationshipHash);
667
+ }
668
+ } else if (relationshipMeta.kind === 'hasMany') {
669
+ if (relationshipHash) {
670
+ data = new Array(relationshipHash.length);
671
+ if (relationshipMeta.options.polymorphic) {
672
+ for (let i = 0, l = relationshipHash.length; i < l; i++) {
673
+ const item = relationshipHash[i];
674
+ data[i] = this.extractPolymorphicRelationship(relationshipMeta.type, item, {
675
+ key,
676
+ resourceHash,
677
+ relationshipMeta
678
+ });
679
+ }
680
+ } else {
681
+ for (let i = 0, l = relationshipHash.length; i < l; i++) {
682
+ const item = relationshipHash[i];
683
+ data[i] = this.extractRelationship(relationshipMeta.type, item);
684
+ }
685
+ }
686
+ }
687
+ }
688
+ relationship = {
689
+ data
690
+ };
691
+ }
692
+ const linkKey = this.keyForLink(key, relationshipMeta.kind);
693
+ if (resourceHash.links && resourceHash.links[linkKey] !== undefined) {
694
+ const related = resourceHash.links[linkKey];
695
+ relationship = relationship || {};
696
+ relationship.links = {
697
+ related
698
+ };
699
+ }
700
+ if (relationship) {
701
+ relationships[key] = relationship;
702
+ }
703
+ });
704
+ return relationships;
705
+ },
706
+ /**
707
+ Dasherizes the model name in the payload
708
+ @method modelNameFromPayloadKey
709
+ @public
710
+ @param {String} key
711
+ @return {String} the model's modelName
712
+ */
713
+ modelNameFromPayloadKey(key) {
714
+ return dasherize(singularize(key));
715
+ },
716
+ /**
717
+ @method normalizeRelationships
718
+ @private
719
+ */
720
+ normalizeRelationships(typeClass, hash) {
721
+ let payloadKey;
722
+ if (this.keyForRelationship) {
723
+ typeClass.eachRelationship((key, relationship) => {
724
+ payloadKey = this.keyForRelationship(key, relationship.kind, 'deserialize');
725
+ if (key === payloadKey) {
726
+ return;
727
+ }
728
+ if (hash[payloadKey] === undefined) {
729
+ return;
730
+ }
731
+ hash[key] = hash[payloadKey];
732
+ delete hash[payloadKey];
733
+ });
734
+ }
735
+ },
736
+ /**
737
+ @method normalizeUsingDeclaredMapping
738
+ @private
739
+ */
740
+ normalizeUsingDeclaredMapping(modelClass, hash) {
741
+ const attrs = this.attrs;
742
+ let normalizedKey;
743
+ let payloadKey;
744
+ if (attrs) {
745
+ for (const key in attrs) {
746
+ normalizedKey = payloadKey = this._getMappedKey(key, modelClass);
747
+ if (hash[payloadKey] === undefined) {
748
+ continue;
749
+ }
750
+ if (modelClass.attributes.has(key)) {
751
+ normalizedKey = this.keyForAttribute(key, 'deserialize');
752
+ }
753
+ if (modelClass.relationshipsByName.has(key)) {
754
+ normalizedKey = this.keyForRelationship(key, modelClass, 'deserialize');
755
+ }
756
+ if (payloadKey !== normalizedKey) {
757
+ hash[normalizedKey] = hash[payloadKey];
758
+ delete hash[payloadKey];
759
+ }
760
+ }
761
+ }
762
+ },
763
+ /**
764
+ Looks up the property key that was set by the custom `attr` mapping
765
+ passed to the serializer.
766
+ @method _getMappedKey
767
+ @private
768
+ @param {String} key
769
+ @return {String} key
770
+ */
771
+ _getMappedKey(key, modelClass) {
772
+ warn('There is no attribute or relationship with the name `' + key + '` on `' + modelClass.modelName + '`. Check your serializers attrs hash.', modelClass.attributes.has(key) || modelClass.relationshipsByName.has(key), {
773
+ id: 'ds.serializer.no-mapped-attrs-key'
774
+ });
775
+ const attrs = this.attrs;
776
+ let mappedKey;
777
+ if (attrs && attrs[key]) {
778
+ mappedKey = attrs[key];
779
+ //We need to account for both the { title: 'post_title' } and
780
+ //{ title: { key: 'post_title' }} forms
781
+ if (mappedKey.key) {
782
+ mappedKey = mappedKey.key;
783
+ }
784
+ if (typeof mappedKey === 'string') {
785
+ key = mappedKey;
786
+ }
787
+ }
788
+ return key;
789
+ },
790
+ /**
791
+ Check attrs.key.serialize property to inform if the `key`
792
+ can be serialized
793
+ @method _canSerialize
794
+ @private
795
+ @param {String} key
796
+ @return {boolean} true if the key can be serialized
797
+ */
798
+ _canSerialize(key) {
799
+ const attrs = this.attrs;
800
+ return !attrs || !attrs[key] || attrs[key].serialize !== false;
801
+ },
802
+ /**
803
+ When attrs.key.serialize is set to true then
804
+ it takes priority over the other checks and the related
805
+ attribute/relationship will be serialized
806
+ @method _mustSerialize
807
+ @private
808
+ @param {String} key
809
+ @return {boolean} true if the key must be serialized
810
+ */
811
+ _mustSerialize(key) {
812
+ const attrs = this.attrs;
813
+ return attrs && attrs[key] && attrs[key].serialize === true;
814
+ },
815
+ /**
816
+ Check if the given hasMany relationship should be serialized
817
+ By default only many-to-many and many-to-none relationships are serialized.
818
+ This could be configured per relationship by Serializer's `attrs` object.
819
+ @method shouldSerializeHasMany
820
+ @public
821
+ @param {Snapshot} snapshot
822
+ @param {String} key
823
+ @param {RelationshipSchema} relationship
824
+ @return {boolean} true if the hasMany relationship should be serialized
825
+ */
826
+ shouldSerializeHasMany(snapshot, key, relationship) {
827
+ const schema = this.store.modelFor(snapshot.modelName);
828
+ const relationshipType = schema.determineRelationshipType(relationship, this.store);
829
+ if (this._mustSerialize(key)) {
830
+ return true;
831
+ }
832
+ return this._canSerialize(key) && (relationshipType === 'manyToNone' || relationshipType === 'manyToMany');
833
+ },
834
+ // SERIALIZE
835
+ /**
836
+ Called when a record is saved in order to convert the
837
+ record into JSON.
838
+ By default, it creates a JSON object with a key for
839
+ each attribute and belongsTo relationship.
840
+ For example, consider this model:
841
+ ```app/models/comment.js
842
+ import Model, { attr, belongsTo } from '@ember-data-mirror/model';
843
+ export default class CommentModel extends Model {
844
+ @attr title;
845
+ @attr body;
846
+ @belongsTo('user') author;
847
+ }
848
+ ```
849
+ The default serialization would create a JSON object like:
850
+ ```javascript
851
+ {
852
+ "title": "Rails is unagi",
853
+ "body": "Rails? Omakase? O_O",
854
+ "author": 12
855
+ }
856
+ ```
857
+ By default, attributes are passed through as-is, unless
858
+ you specified an attribute type (`attr('date')`). If
859
+ you specify a transform, the JavaScript value will be
860
+ serialized when inserted into the JSON hash.
861
+ By default, belongs-to relationships are converted into
862
+ IDs when inserted into the JSON hash.
863
+ ## IDs
864
+ `serialize` takes an options hash with a single option:
865
+ `includeId`. If this option is `true`, `serialize` will,
866
+ by default include the ID in the JSON object it builds.
867
+ The adapter passes in `includeId: true` when serializing
868
+ a record for `createRecord`, but not for `updateRecord`.
869
+ ## Customization
870
+ Your server may expect a different JSON format than the
871
+ built-in serialization format.
872
+ In that case, you can implement `serialize` yourself and
873
+ return a JSON hash of your choosing.
874
+ ```app/serializers/post.js
875
+ import JSONSerializer from '@ember-data-mirror/serializer/json';
876
+ export default class PostSerializer extends JSONSerializer {
877
+ serialize(snapshot, options) {
878
+ let json = {
879
+ POST_TTL: snapshot.attr('title'),
880
+ POST_BDY: snapshot.attr('body'),
881
+ POST_CMS: snapshot.hasMany('comments', { ids: true })
882
+ };
883
+ if (options.includeId) {
884
+ json.POST_ID_ = snapshot.id;
885
+ }
886
+ return json;
887
+ }
888
+ }
889
+ ```
890
+ ## Customizing an App-Wide Serializer
891
+ If you want to define a serializer for your entire
892
+ application, you'll probably want to use `eachAttribute`
893
+ and `eachRelationship` on the record.
894
+ ```app/serializers/application.js
895
+ import JSONSerializer from '@ember-data-mirror/serializer/json';
896
+ import { singularize } from '<app-name>/utils/string-utils';
897
+ export default class ApplicationSerializer extends JSONSerializer {
898
+ serialize(snapshot, options) {
899
+ let json = {};
900
+ snapshot.eachAttribute((name) => {
901
+ json[serverAttributeName(name)] = snapshot.attr(name);
902
+ });
903
+ snapshot.eachRelationship((name, relationship) => {
904
+ if (relationship.kind === 'hasMany') {
905
+ json[serverHasManyName(name)] = snapshot.hasMany(name, { ids: true });
906
+ }
907
+ });
908
+ if (options.includeId) {
909
+ json.ID_ = snapshot.id;
910
+ }
911
+ return json;
912
+ }
913
+ }
914
+ function serverAttributeName(attribute) {
915
+ return attribute.underscore().toUpperCase();
916
+ }
917
+ function serverHasManyName(name) {
918
+ return serverAttributeName(singularize(name)) + "_IDS";
919
+ }
920
+ ```
921
+ This serializer will generate JSON that looks like this:
922
+ ```javascript
923
+ {
924
+ "TITLE": "Rails is omakase",
925
+ "BODY": "Yep. Omakase.",
926
+ "COMMENT_IDS": [ "1", "2", "3" ]
927
+ }
928
+ ```
929
+ ## Tweaking the Default JSON
930
+ If you just want to do some small tweaks on the default JSON,
931
+ you can call `super.serialize` first and make the tweaks on
932
+ the returned JSON.
933
+ ```app/serializers/post.js
934
+ import JSONSerializer from '@ember-data-mirror/serializer/json';
935
+ export default class PostSerializer extends JSONSerializer {
936
+ serialize(snapshot, options) {
937
+ let json = super.serialize(...arguments);
938
+ json.subject = json.title;
939
+ delete json.title;
940
+ return json;
941
+ }
942
+ }
943
+ ```
944
+ @method serialize
945
+ @public
946
+ @param {Snapshot} snapshot
947
+ @param {Object} options
948
+ @return {Object} json
949
+ */
950
+ serialize(snapshot, options) {
951
+ const json = {};
952
+ if (options && options.includeId) {
953
+ const id = snapshot.id;
954
+ if (id) {
955
+ json[this.primaryKey] = id;
956
+ }
957
+ }
958
+ snapshot.eachAttribute((key, attribute) => {
959
+ this.serializeAttribute(snapshot, json, key, attribute);
960
+ });
961
+ snapshot.eachRelationship((key, relationship) => {
962
+ if (relationship.kind === 'belongsTo') {
963
+ this.serializeBelongsTo(snapshot, json, relationship);
964
+ } else if (relationship.kind === 'hasMany') {
965
+ this.serializeHasMany(snapshot, json, relationship);
966
+ }
967
+ });
968
+ return json;
969
+ },
970
+ /**
971
+ You can use this method to customize how a serialized record is added to the complete
972
+ JSON hash to be sent to the server. By default the JSON Serializer does not namespace
973
+ the payload and just sends the raw serialized JSON object.
974
+ If your server expects namespaced keys, you should consider using the RESTSerializer.
975
+ Otherwise you can override this method to customize how the record is added to the hash.
976
+ The hash property should be modified by reference.
977
+ For example, your server may expect underscored root objects.
978
+ ```app/serializers/application.js
979
+ import RESTSerializer from '@ember-data-mirror/serializer/rest';
980
+ import { decamelize } from '<app-name>/utils/string-utils';
981
+ export default class ApplicationSerializer extends RESTSerializer {
982
+ serializeIntoHash(data, type, snapshot, options) {
983
+ let root = decamelize(type.modelName);
984
+ data[root] = this.serialize(snapshot, options);
985
+ }
986
+ }
987
+ ```
988
+ @method serializeIntoHash
989
+ @public
990
+ @param {Object} hash
991
+ @param {Model} typeClass
992
+ @param {Snapshot} snapshot
993
+ @param {Object} options
994
+ */
995
+ serializeIntoHash(hash, typeClass, snapshot, options) {
996
+ Object.assign(hash, this.serialize(snapshot, options));
997
+ },
998
+ /**
999
+ `serializeAttribute` can be used to customize how `attr`
1000
+ properties are serialized
1001
+ For example if you wanted to ensure all your attributes were always
1002
+ serialized as properties on an `attributes` object you could
1003
+ write:
1004
+ ```app/serializers/application.js
1005
+ import JSONSerializer from '@ember-data-mirror/serializer/json';
1006
+ export default class ApplicationSerializer extends JSONSerializer {
1007
+ serializeAttribute(snapshot, json, key, attributes) {
1008
+ json.attributes = json.attributes || {};
1009
+ super.serializeAttribute(snapshot, json.attributes, key, attributes);
1010
+ }
1011
+ }
1012
+ ```
1013
+ @method serializeAttribute
1014
+ @public
1015
+ @param {Snapshot} snapshot
1016
+ @param {Object} json
1017
+ @param {String} key
1018
+ @param {Object} attribute
1019
+ */
1020
+ serializeAttribute(snapshot, json, key, attribute) {
1021
+ if (this._canSerialize(key)) {
1022
+ const type = attribute.type;
1023
+ let value = snapshot.attr(key);
1024
+ if (type) {
1025
+ const transform = this.transformFor(type);
1026
+ value = transform.serialize(value, attribute.options);
1027
+ }
1028
+
1029
+ // if provided, use the mapping provided by `attrs` in
1030
+ // the serializer
1031
+ const schema = this.store.modelFor(snapshot.modelName);
1032
+ let payloadKey = this._getMappedKey(key, schema);
1033
+ if (payloadKey === key && this.keyForAttribute) {
1034
+ payloadKey = this.keyForAttribute(key, 'serialize');
1035
+ }
1036
+ json[payloadKey] = value;
1037
+ }
1038
+ },
1039
+ /**
1040
+ `serializeBelongsTo` can be used to customize how `belongsTo`
1041
+ properties are serialized.
1042
+ Example
1043
+ ```app/serializers/post.js
1044
+ import JSONSerializer from '@ember-data-mirror/serializer/json';
1045
+ export default class PostSerializer extends JSONSerializer {
1046
+ serializeBelongsTo(snapshot, json, relationship) {
1047
+ let key = relationship.name;
1048
+ let belongsTo = snapshot.belongsTo(key);
1049
+ key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo", "serialize") : key;
1050
+ json[key] = !belongsTo ? null : belongsTo.record.toJSON();
1051
+ }
1052
+ }
1053
+ ```
1054
+ @method serializeBelongsTo
1055
+ @public
1056
+ @param {Snapshot} snapshot
1057
+ @param {Object} json
1058
+ @param {Object} relationship
1059
+ */
1060
+ serializeBelongsTo(snapshot, json, relationship) {
1061
+ const name = relationship.name;
1062
+ if (this._canSerialize(name)) {
1063
+ const belongsToId = snapshot.belongsTo(name, {
1064
+ id: true
1065
+ });
1066
+
1067
+ // if provided, use the mapping provided by `attrs` in
1068
+ // the serializer
1069
+ const schema = this.store.modelFor(snapshot.modelName);
1070
+ let payloadKey = this._getMappedKey(name, schema);
1071
+ if (payloadKey === name && this.keyForRelationship) {
1072
+ payloadKey = this.keyForRelationship(name, 'belongsTo', 'serialize');
1073
+ }
1074
+
1075
+ //Need to check whether the id is there for new&async records
1076
+ if (!belongsToId) {
1077
+ json[payloadKey] = null;
1078
+ } else {
1079
+ json[payloadKey] = belongsToId;
1080
+ }
1081
+ if (relationship.options.polymorphic) {
1082
+ this.serializePolymorphicType(snapshot, json, relationship);
1083
+ }
1084
+ }
1085
+ },
1086
+ /**
1087
+ `serializeHasMany` can be used to customize how `hasMany`
1088
+ properties are serialized.
1089
+ Example
1090
+ ```app/serializers/post.js
1091
+ import JSONSerializer from '@ember-data-mirror/serializer/json';
1092
+ export default class PostSerializer extends JSONSerializer {
1093
+ serializeHasMany(snapshot, json, relationship) {
1094
+ let key = relationship.name;
1095
+ if (key === 'comments') {
1096
+ return;
1097
+ } else {
1098
+ super.serializeHasMany(...arguments);
1099
+ }
1100
+ }
1101
+ }
1102
+ ```
1103
+ @method serializeHasMany
1104
+ @public
1105
+ @param {Snapshot} snapshot
1106
+ @param {Object} json
1107
+ @param {Object} relationship
1108
+ */
1109
+ serializeHasMany(snapshot, json, relationship) {
1110
+ const name = relationship.name;
1111
+ if (this.shouldSerializeHasMany(snapshot, name, relationship)) {
1112
+ const hasMany = snapshot.hasMany(name, {
1113
+ ids: true
1114
+ });
1115
+ if (hasMany !== undefined) {
1116
+ // if provided, use the mapping provided by `attrs` in
1117
+ // the serializer
1118
+ const schema = this.store.modelFor(snapshot.modelName);
1119
+ let payloadKey = this._getMappedKey(name, schema);
1120
+ if (payloadKey === name && this.keyForRelationship) {
1121
+ payloadKey = this.keyForRelationship(name, 'hasMany', 'serialize');
1122
+ }
1123
+ json[payloadKey] = hasMany;
1124
+ // TODO support for polymorphic manyToNone and manyToMany relationships
1125
+ }
1126
+ }
1127
+ },
1128
+ /**
1129
+ You can use this method to customize how polymorphic objects are
1130
+ serialized. Objects are considered to be polymorphic if
1131
+ `{ polymorphic: true }` is pass as the second argument to the
1132
+ `belongsTo` function.
1133
+ Example
1134
+ ```app/serializers/comment.js
1135
+ import JSONSerializer from '@ember-data-mirror/serializer/json';
1136
+ export default class CommentSerializer extends JSONSerializer {
1137
+ serializePolymorphicType(snapshot, json, relationship) {
1138
+ let key = relationship.name;
1139
+ let belongsTo = snapshot.belongsTo(key);
1140
+ key = this.keyForAttribute ? this.keyForAttribute(key, 'serialize') : key;
1141
+ if (!belongsTo) {
1142
+ json[key + '_type'] = null;
1143
+ } else {
1144
+ json[key + '_type'] = belongsTo.modelName;
1145
+ }
1146
+ }
1147
+ }
1148
+ ```
1149
+ @method serializePolymorphicType
1150
+ @public
1151
+ @param {Snapshot} snapshot
1152
+ @param {Object} json
1153
+ @param {Object} relationship
1154
+ */
1155
+ serializePolymorphicType() {},
1156
+ /**
1157
+ `extractMeta` is used to deserialize any meta information in the
1158
+ adapter payload. By default Ember Data expects meta information to
1159
+ be located on the `meta` property of the payload object.
1160
+ Example
1161
+ ```app/serializers/post.js
1162
+ import JSONSerializer from '@ember-data-mirror/serializer/json';
1163
+ export default class PostSerializer extends JSONSerializer {
1164
+ extractMeta(store, typeClass, payload) {
1165
+ if (payload && payload.hasOwnProperty('_pagination')) {
1166
+ let meta = payload._pagination;
1167
+ delete payload._pagination;
1168
+ return meta;
1169
+ }
1170
+ }
1171
+ }
1172
+ ```
1173
+ @method extractMeta
1174
+ @public
1175
+ @param {Store} store
1176
+ @param {Model} modelClass
1177
+ @param {Object} payload
1178
+ */
1179
+ extractMeta(store, modelClass, payload) {
1180
+ if (payload && payload['meta'] !== undefined) {
1181
+ const meta = payload.meta;
1182
+ delete payload.meta;
1183
+ return meta;
1184
+ }
1185
+ },
1186
+ /**
1187
+ `extractErrors` is used to extract model errors when a call
1188
+ to `Model#save` fails with an `InvalidError`. By default
1189
+ Ember Data expects error information to be located on the `errors`
1190
+ property of the payload object.
1191
+ This serializer expects this `errors` object to be an Array similar
1192
+ to the following, compliant with the https://jsonapi.org/format/#errors specification:
1193
+ ```js
1194
+ {
1195
+ "errors": [
1196
+ {
1197
+ "detail": "This username is already taken!",
1198
+ "source": {
1199
+ "pointer": "data/attributes/username"
1200
+ }
1201
+ }, {
1202
+ "detail": "Doesn't look like a valid email.",
1203
+ "source": {
1204
+ "pointer": "data/attributes/email"
1205
+ }
1206
+ }
1207
+ ]
1208
+ }
1209
+ ```
1210
+ The key `detail` provides a textual description of the problem.
1211
+ Alternatively, the key `title` can be used for the same purpose.
1212
+ The nested keys `source.pointer` detail which specific element
1213
+ of the request data was invalid.
1214
+ Note that JSON-API also allows for object-level errors to be placed
1215
+ in an object with pointer `data`, signifying that the problem
1216
+ cannot be traced to a specific attribute:
1217
+ ```javascript
1218
+ {
1219
+ "errors": [
1220
+ {
1221
+ "detail": "Some generic non property error message",
1222
+ "source": {
1223
+ "pointer": "data"
1224
+ }
1225
+ }
1226
+ ]
1227
+ }
1228
+ ```
1229
+ When turn into a `Errors` object, you can read these errors
1230
+ through the property `base`:
1231
+ ```handlebars
1232
+ {{#each @model.errors.base as |error|}}
1233
+ <div class="error">
1234
+ {{error.message}}
1235
+ </div>
1236
+ {{/each}}
1237
+ ```
1238
+ Example of alternative implementation, overriding the default
1239
+ behavior to deal with a different format of errors:
1240
+ ```app/serializers/post.js
1241
+ import JSONSerializer from '@ember-data-mirror/serializer/json';
1242
+ export default class PostSerializer extends JSONSerializer {
1243
+ extractErrors(store, typeClass, payload, id) {
1244
+ if (payload && typeof payload === 'object' && payload._problems) {
1245
+ payload = payload._problems;
1246
+ this.normalizeErrors(typeClass, payload);
1247
+ }
1248
+ return payload;
1249
+ }
1250
+ }
1251
+ ```
1252
+ @method extractErrors
1253
+ @public
1254
+ @param {Store} store
1255
+ @param {Model} typeClass
1256
+ @param {Object} payload
1257
+ @param {(String|Number)} id
1258
+ @return {Object} json The deserialized errors
1259
+ */
1260
+ extractErrors(store, typeClass, payload, id) {
1261
+ if (payload && typeof payload === 'object' && payload.errors) {
1262
+ // the default assumption is that errors is already in JSON:API format
1263
+ const extracted = {};
1264
+ payload.errors.forEach(error => {
1265
+ if (error.source && error.source.pointer) {
1266
+ let key = error.source.pointer.match(SOURCE_POINTER_REGEXP);
1267
+ if (key) {
1268
+ key = key[2];
1269
+ } else if (error.source.pointer.search(SOURCE_POINTER_PRIMARY_REGEXP) !== -1) {
1270
+ key = PRIMARY_ATTRIBUTE_KEY;
1271
+ }
1272
+ if (key) {
1273
+ extracted[key] = extracted[key] || [];
1274
+ extracted[key].push(error.detail || error.title);
1275
+ }
1276
+ }
1277
+ });
1278
+
1279
+ // if the user has an attrs hash, convert keys using it
1280
+ this.normalizeUsingDeclaredMapping(typeClass, extracted);
1281
+
1282
+ // for each attr and relationship, make sure that we use
1283
+ // the normalized key
1284
+ typeClass.eachAttribute(name => {
1285
+ const key = this.keyForAttribute(name, 'deserialize');
1286
+ if (key !== name && extracted[key] !== undefined) {
1287
+ extracted[name] = extracted[key];
1288
+ delete extracted[key];
1289
+ }
1290
+ });
1291
+ typeClass.eachRelationship(name => {
1292
+ const key = this.keyForRelationship(name, 'deserialize');
1293
+ if (key !== name && extracted[key] !== undefined) {
1294
+ extracted[name] = extracted[key];
1295
+ delete extracted[key];
1296
+ }
1297
+ });
1298
+ return extracted;
1299
+ }
1300
+ return payload;
1301
+ },
1302
+ /**
1303
+ `keyForAttribute` can be used to define rules for how to convert an
1304
+ attribute name in your model to a key in your JSON.
1305
+ Example
1306
+ ```app/serializers/application.js
1307
+ import JSONSerializer from '@ember-data-mirror/serializer/json';
1308
+ import { underscore } from '<app-name>/utils/string-utils';
1309
+ export default class ApplicationSerializer extends JSONSerializer {
1310
+ keyForAttribute(attr, method) {
1311
+ return underscore(attr).toUpperCase();
1312
+ }
1313
+ }
1314
+ ```
1315
+ @method keyForAttribute
1316
+ @public
1317
+ @param {String} key
1318
+ @param {String} method
1319
+ @return {String} normalized key
1320
+ */
1321
+ keyForAttribute(key, method) {
1322
+ return key;
1323
+ },
1324
+ /**
1325
+ `keyForRelationship` can be used to define a custom key when
1326
+ serializing and deserializing relationship properties. By default
1327
+ `JSONSerializer` does not provide an implementation of this method.
1328
+ Example
1329
+ ```app/serializers/post.js
1330
+ import JSONSerializer from '@ember-data-mirror/serializer/json';
1331
+ import { underscore } from '<app-name>/utils/string-utils';
1332
+ export default class PostSerializer extends JSONSerializer {
1333
+ keyForRelationship(key, relationship, method) {
1334
+ return `rel_${underscore(key)}`;
1335
+ }
1336
+ }
1337
+ ```
1338
+ @method keyForRelationship
1339
+ @public
1340
+ @param {String} key
1341
+ @param {String} typeClass
1342
+ @param {String} method
1343
+ @return {String} normalized key
1344
+ */
1345
+ keyForRelationship(key, typeClass, method) {
1346
+ return key;
1347
+ },
1348
+ /**
1349
+ `keyForLink` can be used to define a custom key when deserializing link
1350
+ properties.
1351
+ @method keyForLink
1352
+ @public
1353
+ @param {String} key
1354
+ @param {String} kind `belongsTo` or `hasMany`
1355
+ @return {String} normalized key
1356
+ */
1357
+ keyForLink(key, kind) {
1358
+ return key;
1359
+ },
1360
+ // HELPERS
1361
+
1362
+ /**
1363
+ @method transformFor
1364
+ @private
1365
+ @param {String} attributeType
1366
+ @param {Boolean} skipAssertion
1367
+ @return {Transform} transform
1368
+ */
1369
+ transformFor(attributeType, skipAssertion) {
1370
+ const transform = getOwner(this).lookup('transform:' + attributeType);
1371
+ assert(`Unable to find the transform for \`attr('${attributeType}')\``, skipAssertion || !!transform);
1372
+ return transform;
1373
+ }
1374
+ });
1375
+ export { JSONSerializer as default };