serialization
Whenever rendering a Dream instance, Psychic will always be smart enough to leverage the provided serializer, making these two lines equivalent:
export default class PlacesController extends PsychicController {
public async index() {
...
this.ok(places)
this.ok(places.map(place => new PlaceSerializer(place).render()))
}
}
Serialization is an essential step in rendering resourceful output from your application. This is, of course, fairly tricky, since there are many different ways that your application's models may need to be rendered. For example, you may want your index method to render a shortened version of your Place model, while the show method uses the default serializer, which includes nested rooms:
export default class PlacesController extends PsychicController {
public async show() {
const place = await this.currentHost
.associationQuery('places')
.leftJoinPreload('rooms')
.find(this.castParam('id'))
this.ok(place)
}
public async index() {
const places = await this.currentHost.associationQuery('places')
this.ok(places, { serializerKey: 'summary' })
}
}
Note that when specifying a serializerKey, it must match one of the keys in that model's serializers getter:
class Place extends ApplicationModel {
public get serializers(): DreamSerializers<Place> {
return {
default: 'PlaceSerializer',
summary: 'PlaceSummarySerializer',
}
}
}
Adding New Serializers
When you need to add a new serializer for a model, follow these steps:
- Create the serializer function in your serializer file (e.g.,
app/serializers/PlaceSerializer.ts):
export const PlaceForGuestsSerializer = (place: Place, passthrough: { locale: LocalesEnum }) =>
DreamSerializer(Place, place)
.attribute('id')
.delegatedAttribute('currentLocalizedText', 'title', { openapi: 'string' })
.customAttribute('displayStyle', () => i18n(passthrough.locale, `places.style.${place.style}`), {
openapi: 'string',
})
- Sync:
pnpm psy sync
- Add the serializer key to the model's
serializersgetter:
export default class Place extends ApplicationModel {
public get serializers(): DreamSerializers<Place> {
return {
default: 'PlaceSerializer',
summary: 'PlaceSummarySerializer',
forGuests: 'PlaceForGuestsSerializer', // Add your new serializer key here
}
}
}
- Use the serializer in your controller by specifying the
serializerKeyin the@OpenAPIdecorator:
@OpenAPI(Place, {
status: 200,
serializerKey: 'forGuests', // This must match a key in the model's serializers getter
})
public async show() {
this.ok(await Place.findOrFail(this.castParam('id')))
}
Alternatively, you can specify the serializerKey when calling this.ok():
public async index() {
const places = await Place.all()
this.ok(places, { serializerKey: 'forGuests' })
}
In addition to being able to do implicit and explicit serializer lookups, Psychic can also simply be handed a serializer (or an array of serializers), in which case it will simply call render on that serializer:
export default class PlacesController extends PsychicController {
public async show() {
this.ok(new PlaceSerializer(place))
}
}
passthrough
In some cases, you may need to send passthrough data to your serializers. This is useful, for example, if you have top level variables that you need to expose to all serializers, such as the locale for the current user.
// src/app/controllers/V1/Guest/PlacesController.ts
export default class V1GuestPlacesController extends V1GuestBaseController {
@OpenAPI(Place, {
status: 200,
many: true,
serializerKey: 'summaryForGuests',
})
public async index() {
this.ok(
await Place
.passthrough({ locale: this.locale })
.preloadFor('summaryForGuests')
.all()
)
}
@OpenAPI(Place, {
status: 200,
serializerKey: 'forGuests',
})
public async show() {
this.ok(
await Place.passthrough({ locale: this.locale })
.preloadFor('forGuests')
.findOrFail(this.castParam('id', 'bigint'))
)
}
...
}
// src/app/models/Place.ts
export default class Place extends ApplicationModel {
public override get table() {
return 'places' as const
}
public get serializers(): DreamSerializers<Place> {
return {
default: 'PlaceSerializer',
summary: 'PlaceSummarySerializer',
summaryForGuests: 'PlaceSummaryForGuestsSerializer',
forGuests: 'PlaceForGuestsSerializer',
}
}
...
}
// src/app/serializers/PlaceSerializer.ts
export const PlaceSummaryForGuestsSerializer = (place: Place) =>
DreamSerializer(Place, place)
.attribute('id')
.delegatedAttribute('currentLocalizedText', 'title', { openapi: 'string' })
export const PlaceForGuestsSerializer = (place: Place, passthrough: { locale: LocalesEnum }) =>
PlaceSummaryForGuestsSerializer(place)
.customAttribute('style', () => i18n(passthrough.locale, `places.style.${place.style}`), {
openapi: 'string',
})
.attribute('sleeps')
.rendersMany('rooms', { serializerKey: 'forGuests' })
...