如何用TypeScript递归生成Prisma风格扁平化泛型筛选键
实现扁平化Prisma筛选条件的TypeScript类型定义
需求背景
Prisma支持嵌套结构的筛选条件,示例如下:
prisma.user.findMany({ where: { name: { contains: 'jeff' }, city: { name: { contains: 'hol' }, state: { acr: { equals: 'vegas' } } } } })
给定如下对象结构:
{ name: '', city: { name: '', state: { acr: '' } } }
需要生成扁平化的筛选条件类型,而非嵌套结构:
type UserFilters = { name_contains?: string, name_equals?: string, city_name_contains?: string city_name_equals?: string city_state_acr_contains?: string city_state_acr_equals?: string }
当前实现的问题
原类型定义会生成嵌套结构,不符合需求:
type FilterR<Form> = Form extends object ? Partial<{ [Key in string & keyof Form as `${Key}_${'contains' | 'equals'}`]: FilterR<Form[Key]> }> : Form
生成的嵌套结果示例:
Partial<{ name_contains: ""; name_equals: ""; city_contains: Partial<{ name_contains: ""; name_equals: ""; state_contains: Partial<{ acr_contains: ""; acr_equals: ""; }>; state_equals: Partial<{ acr_contains: ""; acr_equals: ""; }>; }>; city_equals: Partial<...>; }>
解决方案
通过递归遍历对象层级,仅在叶子节点(基础类型)生成筛选后缀,中间节点负责拼接路径,最终合并为扁平化对象:
// 辅助类型:递归生成所有扁平化的筛选键值对 type FlattenFilter<T, Path extends string = ""> = T extends object ? { [K in keyof T & string]: FlattenFilter<T[K], `${Path}${Path extends "" ? "" : "_"}${K}`> }[keyof T & string] : { [Op in "contains" | "equals"]: { [Key in `${Path}_${Op}`]: T } }[keyof "contains" | "equals"]; // 将联合类型的键值对合并为单个对象 type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer R) => void ? R : never; // 最终的筛选类型,所有属性可选 type FilterR<T> = Partial<UnionToIntersection<FlattenFilter<T>>>;
用法示例
// 定义目标对象结构 const userSchema = { name: '', city: { name: '', state: { acr: '' } } }; // 生成扁平化筛选类型 type UserFilters = FilterR<typeof userSchema>;
生成的UserFilters类型与期望完全一致,所有筛选键均为下划线拼接的扁平化格式,属性均为可选。
实现逻辑说明
- FlattenFilter:递归遍历对象的每个层级,遇到对象类型时继续拼接路径,遇到基础类型时生成
路径_contains和路径_equals两个键值对 - UnionToIntersection:将递归生成的多个键值对联合类型,转换为交叉类型以合并成单一对象
- Partial:将所有属性设为可选,符合Prisma筛选条件的可选特性
内容的提问来源于stack exchange,提问作者Lucas Steffen
相关产品推荐
相关产品推荐

