Skip to main content

serialization

Whenever rendering a dream instance, Psychic will always be smart enough to leverage the provided serializer, making these two lines equivilent:

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',
}
}
}

In addition to being able to do implicit and explicit serilaizer 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' })

...