普林亞JSON字段也能強(qiáng)類型化?當(dāng)然可以!
SQL数据库系统为我们提供了许多好处,其中最重要的是强表结构约束性。确实,当需要变更表结构时,你需要承担迁移的成本,但收益更为显著:你的代码可以假设所有数据都符合预期格式,因此代码更加清晰整洁。
然而,偶尔我们也想摆脱这种强有力的保证,出于正当理由。你可能有一些小对象想附加到主实体上(例如,图片的元数据),而不将它们定义为单独的一张表。或者你可能需要存储有许多可能稀疏字段的记录,但又不想创建宽表,以免增加复杂性。
Prisma的JSON类型为这种场景提供了一个灵活处理方式。它允许存储任意类型的数据,并在查询结果返回一个通用的JsonValue
类型。
// schema.prisma
model 图片 {
id Int @id @default(autoincrement())
元数据 Json
}
全屏模式 退出全屏
// TS代码
type Metadata {
width: number;
height: number;
format: string;
};
const image = await prisma.image.findFirstOrThrow();
// 显式转换为目标类型
const metadata = (image.metadata as Metadata);
console.log('图片的尺寸是:', metadata.width, '乘', metadata.height);
点击进入全屏。点击退出全屏。
这并不总是很理想,实际上,许多人在使用JSON类型时采用“受控”的方式——仅存储特定形状的数据。因此,恢复一些强类型的功能将非常有帮助。
ZenStack 的新“强类型 JSON 字段”功能旨在解决这一需求。它允许你在模式中定义 JSON 数据的结构,并将 PrismaClient
“修正”为返回具有正确类型的。该功能目前处于预览阶段,仅支持 PostgreSQL。
第一步是使用新的 type
关键字来定义 ZModel 架构(一种从 Prisma 架构扩展而来的 DSL)中的 JSON 数据的形状:
// schema.zmodel
type 元数据结构 {
宽度 Int
高度 Int
类型 String
}
model 图像数据 {
id Int @id @default(自动递增())
元数据 @json
}
全屏模式 退出全屏
类型拥有类似于模型的结构,但不会映射到数据库表。它们仅用于类型定义和验证。你不能在类型中与模型建立关系。然而,你可以包含其他类型的字段来创建嵌套结构。
当你运行 zenstack generate
时,编译器会将带有类型的 JSON 字段转换回 Prisma Json
类型:
// schema.prisma
model 图像 {
id Int @id @default(autoincrement())
元数据 Json
}
全屏显示 退出全屏
那么,Metadata
类型去哪里了呢?它实际上被编译成了 TypeScript 类型声明,当你使用 ZenStack 增强的 PrismaClient
时,这个类型声明会被用来对查询结果进行类型化。
// TS 代码
import { enhance } from '@zenstackhq/runtime';
const db = enhance(prisma);
const image = await db.image.findFirstOrThrow();
// image.metadata 现在直接被类型定义为
// { width: number, height: number, format: string }
console.log('控制台输出: \'图片尺寸: \', image.metadata.width, 'x', image.metadata.height);
全屏, 退出全屏
当你创建或更新时,输入会被正确地类型定义,这样你可以享受自动补全和类型检查带来的便利。
// TS 代码
await db.image.create({
data: {
metadata: {
width: 1920,
height: '1080', // <- 这里,类型不匹配
format: 'jpeg'
}
}
});
全屏模式(按 Esc 键退出)
简单明了,不是吗?但这还不仅如此。
来点运行时的检查怎么样?对于变异情况,ZenStack 还会在运行时通过从类型声明中派生出 Zod 架构来验证输入数据的形状。你也可以像为模型添加约束一样,为字段添加额外的约束:
// schema.zmodel
类型 元数据 {
宽度 Int @gt(0) @lt(10000)
高度 Int @gt(0) @lt(10000)
格式类型 String
}
切换到全屏模式 退出全屏模式
违反这些约束的变异调用会被拒绝:
// TypeScript 代码
// 代码用于创建一个具有特定元数据的图像,包括宽度、高度和格式
await db.image.create({
data: {
metadata: {
width: 1920,
height: 10800, // <- 运行时出错在这里
format: 'jpeg'
}
}
});
全屏模式 退出全屏
调用增强的 Prisma 方法 `image.create` 时出错:策略拒绝了请求,在 'image' 实体的创建检查中失败,输入未通过验证:验证错误:数字必须小于 10000(具体位置为 "metadata.height")
进入全屏。退出全屏。
这真的是类型安全吗?JSON字段可以存储任意类型的数据,因此无法确保数据的一致性。因此,为了保持灵活性,ZenStack不会检查查询结果是否符合类型声明。这意味着如果你知道列包含混合数据,你不能单靠TypeScript类型定义来信任数据。
一种缓解问题的方法是显式地用生成的 Zod 模式来验证数据:
import { MetadataSchema } from '@zenstackhq/runtime/zod/models';
// 获取第一个图片记录
const image = await db.image.findFirstOrThrow();
// 解析图片的元数据
const metadata = MetadataSchema.parse(image.metadata);
进入全屏,退出全屏
接下来目前这个功能还没有涉及到的一个方面是过滤部分。where
子句仍然使用Prisma的Json筛选格式:
// 查找宽度大于102的图片
const images = await db.image.findMany({
where: {
metadata: { path: ['width'], gt: 102 }
}
});
全屏 退出全屏
我们有可能改进这部分,使其像以下这样提供带类型的体验:
const images = await db.image.findMany({
where: {
metadata: { width: { gt: 1024 } }
}
});
全屏 退出
这个有用吗,或者会让人感到困惑(因为它看起来和关系过滤器差不多)?请在下面留言告诉我们。您也可以在官方指南了解更多信息。
关于 ZenStackZenStack 是一个 TypeScript 工具包,系统地增强了 Prisma ORM 的功能。除了强类型的 JSON 字段特性之外,它还提供了一些其他功能,可以大大简化你的全栈开发的过程。
- 模式中的授权规则设定
- 自动创建 RESTful API
- 生成前端查询挂钩
- 等等
确保你查看一下,如果你使用Prisma。
共同學(xué)習(xí),寫下你的評(píng)論
評(píng)論加載中...
作者其他優(yōu)質(zhì)文章