如何用Prisma与TypeScript存储和读取不规则JSON结构?
解决Prisma+TypeScript处理不规则Layout结构的方案
1. Prisma模型定义
在schema.prisma中将Scene的layout字段定义为Json类型(对应PostgreSQL的JSONB,支持高效查询与索引):
model Scene { id String @id @default(cuid()) layout Json // 存储不规则布局结构 // 其他业务字段... }
2. TypeScript联合类型约束Widget结构
针对不同类型的Widget,使用区分联合类型(通过type字段标识Widget类型),让TypeScript能自动推断不同Widget的专属属性:
// 基础Widget类型(所有Widget共通的字段) type BaseWidget = { type: string; }; // 各类型Widget的专属定义 type ChartWidget = BaseWidget & { type: 'chart'; dataSource: string; chartType: 'line' | 'bar'; }; type TextWidget = BaseWidget & { type: 'text'; content: string; fontSize: number; }; type ImageWidget = BaseWidget & { type: 'image'; url: string; alt: string; }; // 所有Widget类型的联合 type Widget = ChartWidget | TextWidget | ImageWidget; // 完整Layout类型定义 type Col = { width: string; widget: Widget; }; type Row = Record<string, Col>; // 支持任意数量的col字段 type Layout = Row[];
3. 读写数据的类型安全处理
查询数据
Prisma返回的layout是Json类型,需转换为自定义的Layout类型,可搭配类型守卫确保结构合法性:
import { prisma } from '@/lib/prisma'; async function getScene(id: string) { const scene = await prisma.scene.findUnique({ where: { id }, }); if (!scene) return null; // 类型断言转换为自定义Layout类型 const typedLayout = scene.layout as unknown as Layout; // 可选:添加类型守卫验证结构有效性 function isValidLayout(layout: unknown): layout is Layout { return Array.isArray(layout) && layout.every(row => typeof row === 'object' && row !== null && Object.values(row).every(col => typeof col === 'object' && col !== null && typeof (col as Col).width === 'string' && typeof (col as Col).widget === 'object' && (col as Col).widget !== null && typeof (col as Col).widget.type === 'string' ) ); } if (isValidLayout(typedLayout)) { return { ...scene, layout: typedLayout }; } else { throw new Error('布局结构不合法'); } }
写入数据
直接传入符合Layout类型的结构即可,Prisma会自动序列化为JSON存储:
async function createScene(layout: Layout) { await prisma.scene.create({ data: { layout }, }); } // 示例使用 const sampleLayout: Layout = [ { col1: { width: '65%', widget: { type: 'chart', dataSource: 'sales-data', chartType: 'line', }, }, col2: { width: '35%', widget: { type: 'text', content: '月度销售报表', fontSize: 16, }, }, }, { col1: { width: '50%', widget: { type: 'image', url: '/banner.png', alt: '销售趋势图', }, }, col2: { width: '50%', widget: { type: 'chart', dataSource: 'user-growth', chartType: 'bar', }, }, }, ]; await createScene(sampleLayout);
4. 可选优化:Prisma客户端类型扩展
如果想让Prisma客户端直接返回Layout类型,无需每次手动断言,可扩展Prisma类型:
创建prisma/extensions.ts:
import { PrismaClient } from '@prisma/client'; import type { Layout } from '@/types/layout'; const prisma = new PrismaClient().$extends({ result: { scene: { layout: { needs: {}, compute(scene) { return scene.layout as unknown as Layout; }, }, }, }, }); export default prisma;
后续直接使用该扩展后的客户端,查询返回的scene.layout将自动为Layout类型。
内容的提问来源于stack exchange,提问作者Paca
相关产品推荐
相关产品推荐

