序列化模型

未匹配的标注

如果你创建 API 服务器,你希望将模型实例转换为纯 JSON 对象,然后再将它们发送到客户端作为响应。

将类实例转换为普通 JSON 对象的过程称为序列化。在序列化过程中,你可能还需要:

  • camelCase模型属性名称转换为snake_case
  • 隐藏/删除 API 响应中的一些属性。例如:从 User 模型中删除password属性。
  • 转换/变异值。例如:将时间戳转换为 ISO 字符串。
  • 添加额外的计算属性。例如:根据用户的名字和姓氏计算fullName

你可以在模型中执行所有这些转换,而无需创建任何单独的转换器或资源类。

注:
在 Edge 模板中使用模型时,无需将模型序列化为 JSON。只有返回 JSON 响应的 API 服务器才需要序列化。

序列化模型

你可以通过调用serializetoJSON方法来序列化模型。例如:

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方法返回一个具有metadata属性的对象。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 字段来查看所有哈希化的密码。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/adonisjs/5.x/mo...

译文地址:https://learnku.com/docs/adonisjs/5.x/mo...

上一篇 下一篇
贡献者:2
讨论数量: 0
发起讨论 只看当前版本


暂无话题~