You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 01:51:29