1 | /**
|
2 | @module @ember-data/serializer
|
3 | */
|
4 |
|
5 | import { makeArray } from '@ember/array';
|
6 | import { assert, deprecate, warn } from '@ember/debug';
|
7 | import { camelize } from '@ember/string';
|
8 | import { isNone, typeOf } from '@ember/utils';
|
9 | import { DEBUG } from '@glimmer/env';
|
10 |
|
11 | import { singularize } from 'ember-inflector';
|
12 |
|
13 | import JSONSerializer from '@ember-data/serializer/json';
|
14 | import { normalizeModelName } from '@ember-data/store';
|
15 | import { coerceId } from '@ember-data/store/-private';
|
16 |
|
17 | import { modelHasAttributeOrRelationshipNamedType } from './-private';
|
18 |
|
19 | /**
|
20 | Normally, applications will use the `RESTSerializer` by implementing
|
21 | the `normalize` method.
|
22 |
|
23 | This allows you to do whatever kind of munging you need and is
|
24 | especially useful if your server is inconsistent and you need to
|
25 | do munging differently for many different kinds of responses.
|
26 |
|
27 | See the `normalize` documentation for more information.
|
28 |
|
29 | ## Across the Board Normalization
|
30 |
|
31 | There are also a number of hooks that you might find useful to define
|
32 | across-the-board rules for your payload. These rules will be useful
|
33 | if your server is consistent, or if you're building an adapter for
|
34 | an infrastructure service, like Firebase, and want to encode service
|
35 | conventions.
|
36 |
|
37 | For example, if all of your keys are underscored and all-caps, but
|
38 | otherwise consistent with the names you use in your models, you
|
39 | can implement across-the-board rules for how to convert an attribute
|
40 | name in your model to a key in your JSON.
|
41 |
|
42 | ```app/serializers/application.js
|
43 | import RESTSerializer from '@ember-data/serializer/rest';
|
44 | import { underscore } from '@ember/string';
|
45 |
|
46 | export default class ApplicationSerializer extends RESTSerializer {
|
47 | keyForAttribute(attr, method) {
|
48 | return underscore(attr).toUpperCase();
|
49 | }
|
50 | }
|
51 | ```
|
52 |
|
53 | You can also implement `keyForRelationship`, which takes the name
|
54 | of the relationship as the first parameter, the kind of
|
55 | relationship (`hasMany` or `belongsTo`) as the second parameter, and
|
56 | the method (`serialize` or `deserialize`) as the third parameter.
|
57 |
|
58 | @class RESTSerializer
|
59 | @extends JSONSerializer
|
60 | */
|
61 | const RESTSerializer = JSONSerializer.extend({
|
62 | /**
|
63 | `keyForPolymorphicType` can be used to define a custom key when
|
64 | serializing and deserializing a polymorphic type. By default, the
|
65 | returned key is `${key}Type`.
|
66 |
|
67 | Example
|
68 |
|
69 | ```app/serializers/post.js
|
70 | import RESTSerializer from '@ember-data/serializer/rest';
|
71 |
|
72 | export default class ApplicationSerializer extends RESTSerializer {
|
73 | keyForPolymorphicType(key, relationship) {
|
74 | let relationshipKey = this.keyForRelationship(key);
|
75 |
|
76 | return 'type-' + relationshipKey;
|
77 | }
|
78 | }
|
79 | ```
|
80 |
|
81 | @method keyForPolymorphicType
|
82 | @param {String} key
|
83 | @param {String} typeClass
|
84 | @param {String} method
|
85 | @return {String} normalized key
|
86 | */
|
87 | keyForPolymorphicType(key, typeClass, method) {
|
88 | let relationshipKey = this.keyForRelationship(key);
|
89 |
|
90 | return `${relationshipKey}Type`;
|
91 | },
|
92 |
|
93 | /**
|
94 | Normalizes a part of the JSON payload returned by
|
95 | the server. You should override this method, munge the hash
|
96 | and call super if you have generic normalization to do.
|
97 |
|
98 | It takes the type of the record that is being normalized
|
99 | (as a Model class), the property where the hash was
|
100 | originally found, and the hash to normalize.
|
101 |
|
102 | For example, if you have a payload that looks like this:
|
103 |
|
104 | ```js
|
105 | {
|
106 | "post": {
|
107 | "id": 1,
|
108 | "title": "Rails is omakase",
|
109 | "comments": [ 1, 2 ]
|
110 | },
|
111 | "comments": [{
|
112 | "id": 1,
|
113 | "body": "FIRST"
|
114 | }, {
|
115 | "id": 2,
|
116 | "body": "Rails is unagi"
|
117 | }]
|
118 | }
|
119 | ```
|
120 |
|
121 | The `normalize` method will be called three times:
|
122 |
|
123 | * With `App.Post`, `"posts"` and `{ id: 1, title: "Rails is omakase", ... }`
|
124 | * With `App.Comment`, `"comments"` and `{ id: 1, body: "FIRST" }`
|
125 | * With `App.Comment`, `"comments"` and `{ id: 2, body: "Rails is unagi" }`
|
126 |
|
127 | You can use this method, for example, to normalize underscored keys to camelized
|
128 | or other general-purpose normalizations. You will only need to implement
|
129 | `normalize` and manipulate the payload as desired.
|
130 |
|
131 | For example, if the `IDs` under `"comments"` are provided as `_id` instead of
|
132 | `id`, you can specify how to normalize just the comments:
|
133 |
|
134 | ```app/serializers/post.js
|
135 | import RESTSerializer from '@ember-data/serializer/rest';
|
136 |
|
137 | export default class ApplicationSerializer extends RESTSerializer {
|
138 | normalize(model, hash, prop) {
|
139 | if (prop === 'comments') {
|
140 | hash.id = hash._id;
|
141 | delete hash._id;
|
142 | }
|
143 |
|
144 | return super.normalize(...arguments);
|
145 | }
|
146 | }
|
147 | ```
|
148 |
|
149 | On each call to the `normalize` method, the third parameter (`prop`) is always
|
150 | one of the keys that were in the original payload or in the result of another
|
151 | normalization as `normalizeResponse`.
|
152 |
|
153 | @method normalize
|
154 | @param {Model} modelClass
|
155 | @param {Object} resourceHash
|
156 | @param {String} prop
|
157 | @return {Object}
|
158 | */
|
159 |
|
160 | /**
|
161 | Normalizes an array of resource payloads and returns a JSON-API Document
|
162 | with primary data and, if any, included data as `{ data, included }`.
|
163 |
|
164 | @method _normalizeArray
|
165 | @param {Store} store
|
166 | @param {String} modelName
|
167 | @param {Object} arrayHash
|
168 | @param {String} prop
|
169 | @return {Object}
|
170 | @private
|
171 | */
|
172 | _normalizeArray(store, modelName, arrayHash, prop) {
|
173 | let documentHash = {
|
174 | data: [],
|
175 | included: [],
|
176 | };
|
177 |
|
178 | let modelClass = store.modelFor(modelName);
|
179 | let serializer = store.serializerFor(modelName);
|
180 |
|
181 | makeArray(arrayHash).forEach(hash => {
|
182 | let { data, included } = this._normalizePolymorphicRecord(store, hash, prop, modelClass, serializer);
|
183 | documentHash.data.push(data);
|
184 | if (included) {
|
185 | documentHash.included.push(...included);
|
186 | }
|
187 | });
|
188 |
|
189 | return documentHash;
|
190 | },
|
191 |
|
192 | _normalizePolymorphicRecord(store, hash, prop, primaryModelClass, primarySerializer) {
|
193 | let serializer = primarySerializer;
|
194 | let modelClass = primaryModelClass;
|
195 |
|
196 | let primaryHasTypeAttribute = modelHasAttributeOrRelationshipNamedType(primaryModelClass);
|
197 |
|
198 | if (!primaryHasTypeAttribute && hash.type) {
|
199 | // Support polymorphic records in async relationships
|
200 | let modelName = this.modelNameFromPayloadKey(hash.type);
|
201 |
|
202 | if (store._hasModelFor(modelName)) {
|
203 | serializer = store.serializerFor(modelName);
|
204 | modelClass = store.modelFor(modelName);
|
205 | }
|
206 | }
|
207 |
|
208 | return serializer.normalize(modelClass, hash, prop);
|
209 | },
|
210 |
|
211 | /*
|
212 | @method _normalizeResponse
|
213 | @param {Store} store
|
214 | @param {Model} primaryModelClass
|
215 | @param {Object} payload
|
216 | @param {String|Number} id
|
217 | @param {String} requestType
|
218 | @param {Boolean} isSingle
|
219 | @return {Object} JSON-API Document
|
220 | @private
|
221 | */
|
222 | _normalizeResponse(store, primaryModelClass, payload, id, requestType, isSingle) {
|
223 | let documentHash = {
|
224 | data: null,
|
225 | included: [],
|
226 | };
|
227 |
|
228 | let meta = this.extractMeta(store, primaryModelClass, payload);
|
229 | if (meta) {
|
230 | assert(
|
231 | 'The `meta` returned from `extractMeta` has to be an object, not "' + typeOf(meta) + '".',
|
232 | typeOf(meta) === 'object'
|
233 | );
|
234 | documentHash.meta = meta;
|
235 | }
|
236 |
|
237 | let keys = Object.keys(payload);
|
238 |
|
239 | for (var i = 0, length = keys.length; i < length; i++) {
|
240 | var prop = keys[i];
|
241 | var modelName = prop;
|
242 | var forcedSecondary = false;
|
243 |
|
244 | /*
|
245 | If you want to provide sideloaded records of the same type that the
|
246 | primary data you can do that by prefixing the key with `_`.
|
247 |
|
248 | Example
|
249 |
|
250 | ```
|
251 | {
|
252 | users: [
|
253 | { id: 1, title: 'Tom', manager: 3 },
|
254 | { id: 2, title: 'Yehuda', manager: 3 }
|
255 | ],
|
256 | _users: [
|
257 | { id: 3, title: 'Tomster' }
|
258 | ]
|
259 | }
|
260 | ```
|
261 |
|
262 | This forces `_users` to be added to `included` instead of `data`.
|
263 | */
|
264 | if (prop.charAt(0) === '_') {
|
265 | forcedSecondary = true;
|
266 | modelName = prop.substr(1);
|
267 | }
|
268 |
|
269 | var typeName = this.modelNameFromPayloadKey(modelName);
|
270 | if (!store._hasModelFor(typeName)) {
|
271 | warn(this.warnMessageNoModelForKey(modelName, typeName), false, {
|
272 | id: 'ds.serializer.model-for-key-missing',
|
273 | });
|
274 | continue;
|
275 | }
|
276 |
|
277 | var isPrimary = !forcedSecondary && this.isPrimaryType(store, typeName, primaryModelClass);
|
278 | var value = payload[prop];
|
279 |
|
280 | if (value === null) {
|
281 | continue;
|
282 | }
|
283 |
|
284 | if (DEBUG) {
|
285 | let isQueryRecordAnArray = requestType === 'queryRecord' && isPrimary && Array.isArray(value);
|
286 | let message =
|
287 | 'The adapter returned an array for the primary data of a `queryRecord` response. This is deprecated as `queryRecord` should return a single record.';
|
288 |
|
289 | deprecate(message, !isQueryRecordAnArray, {
|
290 | id: 'ds.serializer.rest.queryRecord-array-response',
|
291 | until: '3.0',
|
292 | url:
|
293 | 'https://deprecations.emberjs.com/ember-data/v2.x/#toc_store-queryrecord-array-response-with-restserializer',
|
294 | });
|
295 | }
|
296 |
|
297 | /*
|
298 | Support primary data as an object instead of an array.
|
299 |
|
300 | Example
|
301 |
|
302 | ```
|
303 | {
|
304 | user: { id: 1, title: 'Tom', manager: 3 }
|
305 | }
|
306 | ```
|
307 | */
|
308 | if (isPrimary && !Array.isArray(value)) {
|
309 | let { data, included } = this._normalizePolymorphicRecord(store, value, prop, primaryModelClass, this);
|
310 | documentHash.data = data;
|
311 | if (included) {
|
312 | documentHash.included.push(...included);
|
313 | }
|
314 | continue;
|
315 | }
|
316 |
|
317 | let { data, included } = this._normalizeArray(store, typeName, value, prop);
|
318 |
|
319 | if (included) {
|
320 | documentHash.included.push(...included);
|
321 | }
|
322 |
|
323 | if (isSingle) {
|
324 | data.forEach(resource => {
|
325 | /*
|
326 | Figures out if this is the primary record or not.
|
327 |
|
328 | It's either:
|
329 |
|
330 | 1. The record with the same ID as the original request
|
331 | 2. If it's a newly created record without an ID, the first record
|
332 | in the array
|
333 | */
|
334 | let isUpdatedRecord = isPrimary && coerceId(resource.id) === id;
|
335 | let isFirstCreatedRecord = isPrimary && !id && !documentHash.data;
|
336 |
|
337 | if (isFirstCreatedRecord || isUpdatedRecord) {
|
338 | documentHash.data = resource;
|
339 | } else {
|
340 | documentHash.included.push(resource);
|
341 | }
|
342 | });
|
343 | } else {
|
344 | if (isPrimary) {
|
345 | documentHash.data = data;
|
346 | } else {
|
347 | if (data) {
|
348 | documentHash.included.push(...data);
|
349 | }
|
350 | }
|
351 | }
|
352 | }
|
353 |
|
354 | return documentHash;
|
355 | },
|
356 |
|
357 | isPrimaryType(store, modelName, primaryModelClass) {
|
358 | return normalizeModelName(modelName) === primaryModelClass.modelName;
|
359 | },
|
360 |
|
361 | /**
|
362 | This method allows you to push a payload containing top-level
|
363 | collections of records organized per type.
|
364 |
|
365 | ```js
|
366 | {
|
367 | "posts": [{
|
368 | "id": "1",
|
369 | "title": "Rails is omakase",
|
370 | "author", "1",
|
371 | "comments": [ "1" ]
|
372 | }],
|
373 | "comments": [{
|
374 | "id": "1",
|
375 | "body": "FIRST"
|
376 | }],
|
377 | "users": [{
|
378 | "id": "1",
|
379 | "name": "@d2h"
|
380 | }]
|
381 | }
|
382 | ```
|
383 |
|
384 | It will first normalize the payload, so you can use this to push
|
385 | in data streaming in from your server structured the same way
|
386 | that fetches and saves are structured.
|
387 |
|
388 | @method pushPayload
|
389 | @param {Store} store
|
390 | @param {Object} payload
|
391 | */
|
392 | pushPayload(store, payload) {
|
393 | let documentHash = {
|
394 | data: [],
|
395 | included: [],
|
396 | };
|
397 |
|
398 | for (var prop in payload) {
|
399 | var modelName = this.modelNameFromPayloadKey(prop);
|
400 | if (!store._hasModelFor(modelName)) {
|
401 | warn(this.warnMessageNoModelForKey(prop, modelName), false, {
|
402 | id: 'ds.serializer.model-for-key-missing',
|
403 | });
|
404 | continue;
|
405 | }
|
406 | var type = store.modelFor(modelName);
|
407 | var typeSerializer = store.serializerFor(type.modelName);
|
408 |
|
409 | makeArray(payload[prop]).forEach(hash => {
|
410 | let { data, included } = typeSerializer.normalize(type, hash, prop);
|
411 | documentHash.data.push(data);
|
412 | if (included) {
|
413 | documentHash.included.push(...included);
|
414 | }
|
415 | });
|
416 | }
|
417 |
|
418 | store.push(documentHash);
|
419 | },
|
420 |
|
421 | /**
|
422 | This method is used to convert each JSON root key in the payload
|
423 | into a modelName that it can use to look up the appropriate model for
|
424 | that part of the payload.
|
425 |
|
426 | For example, your server may send a model name that does not correspond with
|
427 | the name of the model in your app. Let's take a look at an example model,
|
428 | and an example payload:
|
429 |
|
430 | ```app/models/post.js
|
431 | import Model from '@ember-data/model';
|
432 |
|
433 | export default class Post extends Model {}
|
434 | ```
|
435 |
|
436 | ```javascript
|
437 | {
|
438 | "blog/post": {
|
439 | "id": "1
|
440 | }
|
441 | }
|
442 | ```
|
443 |
|
444 | Ember Data is going to normalize the payload's root key for the modelName. As a result,
|
445 | it will try to look up the "blog/post" model. Since we don't have a model called "blog/post"
|
446 | (or a file called app/models/blog/post.js in ember-cli), Ember Data will throw an error
|
447 | because it cannot find the "blog/post" model.
|
448 |
|
449 | Since we want to remove this namespace, we can define a serializer for the application that will
|
450 | remove "blog/" from the payload key whenver it's encountered by Ember Data:
|
451 |
|
452 | ```app/serializers/application.js
|
453 | import RESTSerializer from '@ember-data/serializer/rest';
|
454 |
|
455 | export default class ApplicationSerializer extends RESTSerializer {
|
456 | modelNameFromPayloadKey(payloadKey) {
|
457 | if (payloadKey === 'blog/post') {
|
458 | return super.modelNameFromPayloadKey(payloadKey.replace('blog/', ''));
|
459 | } else {
|
460 | return super.modelNameFromPayloadKey(payloadKey);
|
461 | }
|
462 | }
|
463 | }
|
464 | ```
|
465 |
|
466 | After refreshing, Ember Data will appropriately look up the "post" model.
|
467 |
|
468 | By default the modelName for a model is its
|
469 | name in dasherized form. This means that a payload key like "blogPost" would be
|
470 | normalized to "blog-post" when Ember Data looks up the model. Usually, Ember Data
|
471 | can use the correct inflection to do this for you. Most of the time, you won't
|
472 | need to override `modelNameFromPayloadKey` for this purpose.
|
473 |
|
474 | @method modelNameFromPayloadKey
|
475 | @param {String} key
|
476 | @return {String} the model's modelName
|
477 | */
|
478 | modelNameFromPayloadKey(key) {
|
479 | return singularize(normalizeModelName(key));
|
480 | },
|
481 |
|
482 | // SERIALIZE
|
483 |
|
484 | /**
|
485 | Called when a record is saved in order to convert the
|
486 | record into JSON.
|
487 |
|
488 | By default, it creates a JSON object with a key for
|
489 | each attribute and belongsTo relationship.
|
490 |
|
491 | For example, consider this model:
|
492 |
|
493 | ```app/models/comment.js
|
494 | import Model, { attr, belongsTo } from '@ember-data/model';
|
495 |
|
496 | export default class Comment extends Model {
|
497 | @attr title
|
498 | @attr body
|
499 |
|
500 | @belongsTo('user') author
|
501 | }
|
502 | ```
|
503 |
|
504 | The default serialization would create a JSON object like:
|
505 |
|
506 | ```js
|
507 | {
|
508 | "title": "Rails is unagi",
|
509 | "body": "Rails? Omakase? O_O",
|
510 | "author": 12
|
511 | }
|
512 | ```
|
513 |
|
514 | By default, attributes are passed through as-is, unless
|
515 | you specified an attribute type (`attr('date')`). If
|
516 | you specify a transform, the JavaScript value will be
|
517 | serialized when inserted into the JSON hash.
|
518 |
|
519 | By default, belongs-to relationships are converted into
|
520 | IDs when inserted into the JSON hash.
|
521 |
|
522 | ## IDs
|
523 |
|
524 | `serialize` takes an options hash with a single option:
|
525 | `includeId`. If this option is `true`, `serialize` will,
|
526 | by default include the ID in the JSON object it builds.
|
527 |
|
528 | The adapter passes in `includeId: true` when serializing
|
529 | a record for `createRecord`, but not for `updateRecord`.
|
530 |
|
531 | ## Customization
|
532 |
|
533 | Your server may expect a different JSON format than the
|
534 | built-in serialization format.
|
535 |
|
536 | In that case, you can implement `serialize` yourself and
|
537 | return a JSON hash of your choosing.
|
538 |
|
539 | ```app/serializers/post.js
|
540 | import RESTSerializer from '@ember-data/serializer/rest';
|
541 |
|
542 | export default class ApplicationSerializer extends RESTSerializer {
|
543 | serialize(snapshot, options) {
|
544 | let json = {
|
545 | POST_TTL: snapshot.attr('title'),
|
546 | POST_BDY: snapshot.attr('body'),
|
547 | POST_CMS: snapshot.hasMany('comments', { ids: true })
|
548 | };
|
549 |
|
550 | if (options.includeId) {
|
551 | json.POST_ID_ = snapshot.id;
|
552 | }
|
553 |
|
554 | return json;
|
555 | }
|
556 | }
|
557 | ```
|
558 |
|
559 | ## Customizing an App-Wide Serializer
|
560 |
|
561 | If you want to define a serializer for your entire
|
562 | application, you'll probably want to use `eachAttribute`
|
563 | and `eachRelationship` on the record.
|
564 |
|
565 | ```app/serializers/application.js
|
566 | import RESTSerializer from '@ember-data/serializer/rest';
|
567 | import { pluralize } from 'ember-inflector';
|
568 |
|
569 | export default class ApplicationSerializer extends RESTSerializer {
|
570 | serialize(snapshot, options) {
|
571 | let json = {};
|
572 |
|
573 | snapshot.eachAttribute(function(name) {
|
574 | json[serverAttributeName(name)] = snapshot.attr(name);
|
575 | });
|
576 |
|
577 | snapshot.eachRelationship(function(name, relationship) {
|
578 | if (relationship.kind === 'hasMany') {
|
579 | json[serverHasManyName(name)] = snapshot.hasMany(name, { ids: true });
|
580 | }
|
581 | });
|
582 |
|
583 | if (options.includeId) {
|
584 | json.ID_ = snapshot.id;
|
585 | }
|
586 |
|
587 | return json;
|
588 | }
|
589 | }
|
590 |
|
591 | function serverAttributeName(attribute) {
|
592 | return attribute.underscore().toUpperCase();
|
593 | }
|
594 |
|
595 | function serverHasManyName(name) {
|
596 | return serverAttributeName(singularize(name)) + "_IDS";
|
597 | }
|
598 | ```
|
599 |
|
600 | This serializer will generate JSON that looks like this:
|
601 |
|
602 | ```js
|
603 | {
|
604 | "TITLE": "Rails is omakase",
|
605 | "BODY": "Yep. Omakase.",
|
606 | "COMMENT_IDS": [ 1, 2, 3 ]
|
607 | }
|
608 | ```
|
609 |
|
610 | ## Tweaking the Default JSON
|
611 |
|
612 | If you just want to do some small tweaks on the default JSON,
|
613 | you can call super first and make the tweaks on the returned
|
614 | JSON.
|
615 |
|
616 | ```app/serializers/post.js
|
617 | import RESTSerializer from '@ember-data/serializer/rest';
|
618 |
|
619 | export default class ApplicationSerializer extends RESTSerializer {
|
620 | serialize(snapshot, options) {
|
621 | let json = super.serialize(snapshot, options);
|
622 |
|
623 | json.subject = json.title;
|
624 | delete json.title;
|
625 |
|
626 | return json;
|
627 | }
|
628 | }
|
629 | ```
|
630 |
|
631 | @method serialize
|
632 | @param {Snapshot} snapshot
|
633 | @param {Object} options
|
634 | @return {Object} json
|
635 | */
|
636 | serialize(snapshot, options) {
|
637 | return this._super(...arguments);
|
638 | },
|
639 |
|
640 | /**
|
641 | You can use this method to customize the root keys serialized into the JSON.
|
642 | The hash property should be modified by reference (possibly using something like _.extend)
|
643 | By default the REST Serializer sends the modelName of a model, which is a camelized
|
644 | version of the name.
|
645 |
|
646 | For example, your server may expect underscored root objects.
|
647 |
|
648 | ```app/serializers/application.js
|
649 | import RESTSerializer from '@ember-data/serializer/rest';
|
650 | import { decamelize } from '@ember/string';
|
651 |
|
652 | export default class ApplicationSerializer extends RESTSerializer {
|
653 | serializeIntoHash(data, type, record, options) {
|
654 | let root = decamelize(type.modelName);
|
655 | data[root] = this.serialize(record, options);
|
656 | }
|
657 | }
|
658 | ```
|
659 |
|
660 | @method serializeIntoHash
|
661 | @param {Object} hash
|
662 | @param {Model} typeClass
|
663 | @param {Snapshot} snapshot
|
664 | @param {Object} options
|
665 | */
|
666 | serializeIntoHash(hash, typeClass, snapshot, options) {
|
667 | let normalizedRootKey = this.payloadKeyFromModelName(typeClass.modelName);
|
668 | hash[normalizedRootKey] = this.serialize(snapshot, options);
|
669 | },
|
670 |
|
671 | /**
|
672 | You can use `payloadKeyFromModelName` to override the root key for an outgoing
|
673 | request. By default, the RESTSerializer returns a camelized version of the
|
674 | model's name.
|
675 |
|
676 | For a model called TacoParty, its `modelName` would be the string `taco-party`. The RESTSerializer
|
677 | will send it to the server with `tacoParty` as the root key in the JSON payload:
|
678 |
|
679 | ```js
|
680 | {
|
681 | "tacoParty": {
|
682 | "id": "1",
|
683 | "location": "Matthew Beale's House"
|
684 | }
|
685 | }
|
686 | ```
|
687 |
|
688 | For example, your server may expect dasherized root objects:
|
689 |
|
690 | ```app/serializers/application.js
|
691 | import RESTSerializer from '@ember-data/serializer/rest';
|
692 | import { dasherize } from '@ember/string';
|
693 |
|
694 | export default class ApplicationSerializer extends RESTSerializer {
|
695 | payloadKeyFromModelName(modelName) {
|
696 | return dasherize(modelName);
|
697 | }
|
698 | }
|
699 | ```
|
700 |
|
701 | Given a `TacoParty` model, calling `save` on it would produce an outgoing
|
702 | request like:
|
703 |
|
704 | ```js
|
705 | {
|
706 | "taco-party": {
|
707 | "id": "1",
|
708 | "location": "Matthew Beale's House"
|
709 | }
|
710 | }
|
711 | ```
|
712 |
|
713 | @method payloadKeyFromModelName
|
714 | @param {String} modelName
|
715 | @return {String}
|
716 | */
|
717 | payloadKeyFromModelName(modelName) {
|
718 | return camelize(modelName);
|
719 | },
|
720 |
|
721 | /**
|
722 | You can use this method to customize how polymorphic objects are serialized.
|
723 | By default the REST Serializer creates the key by appending `Type` to
|
724 | the attribute and value from the model's camelcased model name.
|
725 |
|
726 | @method serializePolymorphicType
|
727 | @param {Snapshot} snapshot
|
728 | @param {Object} json
|
729 | @param {Object} relationship
|
730 | */
|
731 | serializePolymorphicType(snapshot, json, relationship) {
|
732 | let key = relationship.key;
|
733 | let typeKey = this.keyForPolymorphicType(key, relationship.type, 'serialize');
|
734 | let belongsTo = snapshot.belongsTo(key);
|
735 |
|
736 | if (isNone(belongsTo)) {
|
737 | json[typeKey] = null;
|
738 | } else {
|
739 | json[typeKey] = camelize(belongsTo.modelName);
|
740 | }
|
741 | },
|
742 |
|
743 | /**
|
744 | You can use this method to customize how a polymorphic relationship should
|
745 | be extracted.
|
746 |
|
747 | @method extractPolymorphicRelationship
|
748 | @param {Object} relationshipType
|
749 | @param {Object} relationshipHash
|
750 | @param {Object} relationshipOptions
|
751 | @return {Object}
|
752 | */
|
753 | extractPolymorphicRelationship(relationshipType, relationshipHash, relationshipOptions) {
|
754 | let { key, resourceHash, relationshipMeta } = relationshipOptions;
|
755 |
|
756 | // A polymorphic belongsTo relationship can be present in the payload
|
757 | // either in the form where the `id` and the `type` are given:
|
758 | //
|
759 | // {
|
760 | // message: { id: 1, type: 'post' }
|
761 | // }
|
762 | //
|
763 | // or by the `id` and a `<relationship>Type` attribute:
|
764 | //
|
765 | // {
|
766 | // message: 1,
|
767 | // messageType: 'post'
|
768 | // }
|
769 | //
|
770 | // The next code checks if the latter case is present and returns the
|
771 | // corresponding JSON-API representation. The former case is handled within
|
772 | // the base class JSONSerializer.
|
773 | let isPolymorphic = relationshipMeta.options.polymorphic;
|
774 | let typeProperty = this.keyForPolymorphicType(key, relationshipType, 'deserialize');
|
775 |
|
776 | if (isPolymorphic && resourceHash[typeProperty] !== undefined && typeof relationshipHash !== 'object') {
|
777 | let type = this.modelNameFromPayloadKey(resourceHash[typeProperty]);
|
778 | return {
|
779 | id: relationshipHash,
|
780 | type: type,
|
781 | };
|
782 | }
|
783 |
|
784 | return this._super(...arguments);
|
785 | },
|
786 | });
|
787 |
|
788 | if (DEBUG) {
|
789 | RESTSerializer.reopen({
|
790 | warnMessageNoModelForKey(prop, typeKey) {
|
791 | return (
|
792 | 'Encountered "' +
|
793 | prop +
|
794 | '" in payload, but no model was found for model name "' +
|
795 | typeKey +
|
796 | '" (resolved model name using ' +
|
797 | this.constructor.toString() +
|
798 | '.modelNameFromPayloadKey("' +
|
799 | prop +
|
800 | '"))'
|
801 | );
|
802 | },
|
803 | });
|
804 | }
|
805 |
|
806 | export { EmbeddedRecordsMixin } from './-private';
|
807 |
|
808 | export default RESTSerializer;
|