序列化模型
如果你创建 API 服务器,你希望将模型实例转换为纯 JSON 对象,然后再将它们发送到客户端作为响应。
将类实例转换为普通 JSON 对象的过程称为序列化。在序列化过程中,你可能还需要:
- 将
camelCase
模型属性名称转换为snake_case
。 - 隐藏/删除 API 响应中的一些属性。例如:从 User 模型中删除
password
属性。 - 转换/变异值。例如:将时间戳转换为 ISO 字符串。
- 添加额外的计算属性。例如:根据用户的名字和姓氏计算
fullName
。
你可以在模型中执行所有这些转换,而无需创建任何单独的转换器或资源类。
注:
在 Edge 模板中使用模型时,无需将模型序列化为 JSON。只有返回 JSON 响应的 API 服务器才需要序列化。
序列化模型
你可以通过调用serialize
或toJSON
方法来序列化模型。例如:
const post = await Post.find(1)
const postJSON = post.serialize()
您可以通过调用Array.map
方法来序列化模型实例数组。
const posts = await Post.all()
const postsJSON = posts.map((post) => post.serialize())
序列化分页结果
在处理分页结果时,你可以通过在分页器实例上调用.serialize
方法来序列化模型。
paginator.serialize
方法返回一个具有meta
和data
属性的对象。meta
是分页元数据,data
是序列化模型的数组。
const posts = await Post.query().paginate(1)
const paginationJSON = posts.serialize()
/**
{
meta: {},
data: []
}
*/
计算属性
在序列化过程中,模型使用 @column
装饰器返回一个具有属性的对象。如果要序列化任何其他属性,请使用 @computed
装饰器。
import { DateTime } from 'luxon'
import { string } from '@ioc:Adonis/Core/Helpers'
import { BaseModel, column, computed } from '@ioc:Adonis/Lucid/Orm'
export default class Post extends BaseModel {
@column({ isPrimary: true })
public id: number
@column()
public body: string
@computed()
public get excerpt() {
return string.truncate(this.body, 50)
}
}
重命名属性
你可以使用 serializeAs
选项重命名序列化的属性名称,你仍将通过模型上的实际名称访问该属性,但序列化输出将使用 serializeAs
名。例如:
注意:如果要覆盖所有序列化属性的命名约定,请使用 模型命名策略。
import { DateTime } from 'luxon'
import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'
export default class Post extends BaseModel {
@column({ isPrimary: true })
public id: number
@column({ serializeAs: 'content' })
public body: string
}
const post = await Post.find(1)
post.serialize()
/**
{
id: 1,
content: 'Adonis 101'
}
*/
隐藏属性
你可以通过将 serializeAs
值设置为 null
从序列化输出中删除模型属性。例如:
import { DateTime } from 'luxon'
import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'
export default class User extends BaseModel {
@column({ isPrimary: true })
public id: number
@column()
public email: string
@column({ serializeAs: null })
public password: string
}
const user = await User.find(1)
user.serialize()
/**
{
id: 1,
email: 'virk@adonisjs.com'
}
*/
变异/转换值
你还可以通过定义 serialize
方法在序列化期间转换属性值。它接收属性的当前值,并将返回值传递给序列化输出。
注意:请确保模型不受
null
值的影响。
import { DateTime } from 'luxon'
import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'
export default class Post extends BaseModel {
@column({ isPrimary: true })
public id: number
@column.dateTime({
autoCreate: true,
serialize: (value: DateTime | null) => {
return value ? value.setZone('utc').toISO() : value
},
})
public createdAt: DateTime
}
序列化关系
每次序列化模型实例时,preloaded
关系都会自动序列化。例如:
const posts = await Post
.query()
.preload('comments')
const postsJSON = posts.map((post) => post.serialize())
在上面的示例中,所有帖子的 comments
都将被序列化为 post 对象。例如:
{
id: 1,
title: 'Adonis 101',
comments: [{
id: 1,
content: 'Nice article'
}]
}
你可以通过在关系定义上定义 serializeAs
选项来更改关系属性名称。
import { DateTime } from 'luxon'
import Comment from 'App/Models/Comment'
import { BaseModel, column, hasMany, HasMany } from '@ioc:Adonis/Lucid/Orm'
export default class Post extends BaseModel {
@column({ isPrimary: true })
public id: number
@hasMany(() => Comment, {
serializeAs: 'postComments'
})
public 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'
}]
}
*/
如果不想序列化关系,可以设置 serializeAs = null
。
序列化 $extras
对象
未定义为模型列的查询结果值被移动到 $extras
对象。
在以下查询中,我们使用子查询获取 category_name
。但是,你的模型在 category_name
列中对此一无所知,因此我们将其值移动到 $extras
对象。
const post = await Post
.query()
.select('*')
.select(
Database
.from('categories')
.select('name')
.whereColumn('posts.category_id', 'categories.id')
.limit('1')
.as('category_name')
)
.first()
你可以从模型实例访问 extras 对象,如下所示:
post.$extras.category_name
你还可以通过在模型上定义以下属性来序列化 $extras
对象。
class Post extends BaseModel {
/**
* 按原样序列化 `$extras` 对象
*/
public serializeExtras = true
}
此外,你可以通过将 serializeExtras
属性声明为函数来自定义要从 extras 对象中选择的属性。
class Post extends BaseModel {
public serializeExtras() {
return {
category: {
name: this.$extras.category_name
},
}
}
}
特殊的字段/关系
特殊的 API 是为牢记 API 的使用者而设计的。一些选项可能看起来很冗长或不太直观,但是一旦你从 API 使用者的角度看待它,你就能理解。
选择/省略字段
你可以在序列化过程中传递字段/关系树以从最终结果中选择或省略。例如:
const post = await Post.find(1)
posts.serialize({
fields: {
pick: ['id', 'title', 'createdAt']
}
})
除了选择字段,你还可以将字段定义为 omit
。当两者都指定时,omit
将优先 pick
数组。
const post = await Post.find(1)
posts.serialize({
fields: {
omit: ['createdAt', 'updatedAt']
}
})
挑选关系及其领域
你还可以从关系中挑选完整的关系节点或选择/省略字段。
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'],
},
}
})
序列化树一开始可能看起来很冗长。但是,大多数 API 服务器不定义字段或手动选择/省略,通常从 URL 查询字符串中计算。
注意事项
- 特殊 API 使用序列化属性名称且不是模型属性名称。
- 同样,从 API 使用者的角度来看,他们不知道你在模型上定义的属性名称。他们只能看到 JSON 响应并使用相同的属性名称进行选择。
- 特殊 API 不能覆盖
serializeAs = null
选项。否则,有人可以在 URL 查询字符串中定义password
字段来查看所有哈希化的密码。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。