Serializando modelos
Se você criar um servidor de API, talvez queira converter as instâncias de modelo para objetos JSON simples antes de enviá-las ao cliente em resposta.
O processo de transformar instâncias de classe para objetos JSON simples é conhecido como serialização. Durante o processo de serialização, você também pode querer:
Converter os nomes de propriedade do modelo
camelCase
parasnake_case
.Ocultar/remover algumas das propriedades das respostas da API. Por exemplo: Remover a propriedade
password
do modelo User.Converter/mutar valores. Por exemplo: Converter os timestamps para uma string ISO.
Adicionar propriedades computadas adicionais. Por exemplo: Calcular o
fullName
do primeiro e último nome do usuário.
Você pode executar todas essas transformações dentro de seus modelos sem criar quaisquer transformadores ou classes de recursos separados.
NOTA
Não há necessidade de serializar seus modelos para JSON ao usá-los dentro dos modelos do Edge. A serialização é necessária apenas para servidores de API que retornam respostas JSON.
Exemplo básico
Você pode serializar um modelo chamando o método serialize
ou toJSON
. Por exemplo:
const post = await Post.find(1)
const postJSON = post.serialize()
Você pode serializar uma matriz de instâncias de modelo chamando o método Array.map
.
const posts = await Post.all()
const postsJSON = posts.map((post) => post.serialize())
Serializando resultados paginados
Ao trabalhar com resultados paginados, você pode serializar os modelos chamando o método .serialize
na instância do paginador.
O método paginator.serialize
retorna um objeto com as propriedades meta
e data
. meta
é o metadado de paginação e data
é uma matriz de modelos serializados.
const posts = await Post.query().paginate(1)
const paginationJSON = posts.serialize()
/**
{
meta: {},
data: []
}
*/
Propriedades computadas
Durante o processo de serialização, o modelo retorna um objeto com propriedades usando o decorador @column
. Se você quiser serializar quaisquer propriedades adicionais, use o decorador @computed
.
import { DateTime } from 'luxon'
import string from '@adonisjs/core/helpers/string'
import { BaseModel, column, computed } from '@adonisjs/lucid/orm'
export default class Post extends BaseModel {
@column({ isPrimary: true })
declare id: number
@column()
declare body: string
@computed()
get excerpt() {
return string.truncate(this.body, 50)
}
}
Renomeando propriedades
Você pode renomear os nomes de propriedades serializadas usando a opção serializeAs
. Você ainda acessará a propriedade pelo seu nome real no modelo, mas a saída serializada usará o nome serializeAs
. Por exemplo:
NOTA
Use Estratégia de nomenclatura de modelo se quiser substituir a convenção de nomenclatura para todas as propriedades serializadas.
import { DateTime } from 'luxon'
import { BaseModel, column } from '@adonisjs/lucid/orm'
export default class Post extends BaseModel {
@column({ isPrimary: true })
declare id: number
@column({ serializeAs: 'content' })
declare body: string
}
const post = await Post.find(1)
post.serialize()
/**
{
id: 1,
content: 'Adonis 101'
}
*/
Ocultando propriedades
Você pode remover as propriedades do modelo da saída serializada definindo o valor serializeAs
como null
. Por exemplo:
import { DateTime } from 'luxon'
import { BaseModel, column } from '@adonisjs/lucid/orm'
export default class User extends BaseModel {
@column({ isPrimary: true })
declare id: number
@column()
declare email: string
@column({ serializeAs: null })
declare password: string
}
const user = await User.find(1)
user.serialize()
/**
{
id: 1,
email: 'virk@adonisjs.com'
}
*/
Mutando/transformando valores
Você também pode transformar um valor de propriedade durante a serialização definindo o método serialize
. Ele recebe o valor atual da propriedade e o valor de retorno é passado para a saída serializada.
NOTA
Certifique-se de proteger a implementação do método contra os valores null
.
import { DateTime } from 'luxon'
import { BaseModel, column } from '@adonisjs/lucid/orm'
export default class Post extends BaseModel {
@column({ isPrimary: true })
declare id: number
@column.dateTime({
autoCreate: true,
serialize: (value: DateTime | null) => {
return value ? value.setZone('utc').toISO() : value
},
})
declare createdAt: DateTime
}
Serializando relacionamentos
Os relacionamentos preloaded
são serializados automaticamente toda vez que você serializa uma instância de modelo. Por exemplo:
const posts = await Post.query().preload('comments')
const postsJSON = posts.map((post) => post.serialize())
No exemplo acima, os comentários
para todas as postagens serão serializados para o objeto de postagem. Por exemplo:
{
id: 1,
title: 'Adonis 101',
comments: [{
id: 1,
content: 'Nice article'
}]
}
Você pode alterar o nome da propriedade do relacionamento definindo a opção serializeAs
na definição do relacionamento.
import { DateTime } from 'luxon'
import Comment from '#models/comment'
import type { HasMany } from '@adonisjs/lucid/types/relations'
import { BaseModel, column, hasMany } from '@adonisjs/lucid/orm'
export default class Post extends BaseModel {
@column({ isPrimary: true })
declare id: number
@hasMany(() => Comment, {
serializeAs: 'postComments',
})
comments: HasMany<typeof Comment>
}
const posts = await Post.query().preload('comments')
const postsJSON = posts.map((post) => post.serialize())
/**
{
id: 1,
title: 'Adonis 101',
postComments: [{
id: 1,
content: 'Nice article'
}]
}
*/
Se você não quiser serializar um relacionamento, você pode definir serializeAs = null
.
Serializando objeto $extras
Os valores de resultado da consulta que não são definidos como colunas no modelo são movidos para o objeto $extras
.
Por exemplo, na consulta a seguir, buscamos o category_name
usando uma subconsulta. No entanto, seu modelo não tem conhecimento sobre isso na coluna category_name
em tempo real e, portanto, moveremos seu valor para o objeto $extras
.
const post = await Post.query()
.select('*')
.select(
db
.from('categories')
.select('name')
.whereColumn('posts.category_id', 'categories.id')
.limit('1')
.as('category_name')
)
.first()
Você pode acessar o objeto extras da instância do modelo da seguinte forma:
post.$extras.category_name
Você também pode serializar o objeto $extras
definindo a seguinte propriedade no modelo.
class Post extends BaseModel {
/**
* Serializar o objeto `$extras` como ele está
*/
serializeExtras = true
}
Além disso, você pode personalizar as propriedades que deseja escolher do objeto extras declarando a propriedade serializeExtras
como uma função.
class Post extends BaseModel {
serializeExtras() {
return {
category: {
name: this.$extras.category_name,
},
}
}
}
Seleção seletiva de campos/relacionamentos
A API de seleção seletiva foi projetada tendo o consumidor da API em mente. Algumas das opções podem parecer prolixas ou menos intuitivas, mas quando você olha para elas da perspectiva do consumidor da API, as coisas começam a fazer mais sentido.
Seleção/omissão de campos
Você pode passar uma árvore de campos/relacionamentos para selecionar ou omitir dos resultados finais durante o processo de serialização. Por exemplo:
const post = await Post.find(1)
posts.serialize({
fields: {
pick: ['id', 'title', 'createdAt'],
},
})
Em vez de selecionar campos, você também pode definir os campos para omitir
. Quando ambos são especificados, omit
vencerá o array pick
.
const post = await Post.find(1)
posts.serialize({
fields: {
omit: ['createdAt', 'updatedAt'],
},
})
Seleção de relacionamentos e seus campos
Você também pode selecionar seletivamente os nós de relação completos ou selecionar/omitir campos dos relacionamentos.
const post = await Post.query().preload('comments').preload('category').preload('author').first()
post.serialize({
fields: {
pick: ['id', 'title', 'body'],
},
relations: {
comments: {
fields: ['id', 'body'],
},
author: {
fields: ['id', 'email', 'avatar_url'],
},
},
})
A árvore de serialização pode parecer prolixa a princípio. No entanto, a maioria dos servidores de API não define os campos ou seleciona/omite manualmente e geralmente os calcula a partir da sequência de consulta de URL.
Pontos a serem observados
- A API de seleção seletiva usa os nomes de propriedade de serialização e não os nomes de propriedade do modelo.
- Novamente, do ponto de vista do consumidor da API, eles não sabem o nome da propriedade que você definiu no modelo. Eles só podem ver a resposta JSON e selecionar seletivamente usando os mesmos nomes de propriedade.
- A API de seleção seletiva não pode substituir a opção
serializeAs = null
. Caso contrário, alguém pode definir o campopassword
na sequência de consulta de URL para visualizar todas as senhas com hash.