如何实现支持嵌套与点语法的MongoDB ORM TypeScript过滤类型
TypeScript 实现支持嵌套与点语法的MongoDB过滤类型约束
给定如下TypeScript模型类型:
type MyModel = { string: string subObject: { subObjString: string } }
需要实现一个过滤类型,满足以下输入约束:
- ✅
{ string: 'foo' }(合法) - ✅
{ subObject: { subObjString: 'foo'} }(合法) - ✅
{ 'subObject.subObjString': 'foo' }(合法) - ❌
{ 'subObject.notExistingprop': 'foo' }(非法) - ❌
{ notExisting: 'foo' }(非法)
目标是为MongoDB ORM的过滤输入做类型约束,同时支持传统嵌套对象语法和点分隔的路径语法。用户尝试的FilterType实现存在问题,推导结果为unknown,无法满足需求:
type FilterType<O extends Record<string, any>> = { [K in (keyof O & string)]: O[K] extends Record<string, any> ? K extends symbol ? never : `${K}.${FilterType<O>}` : never }[string] type FilterTypeForModel = FilterType<MyModel> // 被解析为unknown const test1 = ['string', 'subObject'] satisfies FilterTypeForModel // 应合法 const test2 = ['subObject.subObjString'] satisfies FilterTypeForModel // 应合法 const test3 = ['subObject.notExistingProp'] satisfies FilterTypeForModel // 应非法
解决方案
我们需要分两步实现:先递归生成所有合法的点路径字符串,再将点路径与嵌套对象结构合并,最终得到符合要求的过滤类型。
1. 生成所有合法的点路径
实现递归类型PathKeys,遍历对象所有层级生成点分隔路径:
type PathKeys<T extends Record<string, any>> = { [K in keyof T & string]: T[K] extends Record<string, any> ? K | `${K}.${PathKeys<T[K]>}` : K }[keyof T & string]
该类型会递归处理每个键:若当前键对应值为对象,则同时保留当前键和当前键+子对象路径的组合;若为非对象类型,则仅保留当前键。
2. 合并点路径与嵌套对象结构
实现FilterType,同时支持点路径键和嵌套对象两种语法:
type FilterType<T extends Record<string, any>> = // 支持点路径作为键的对象结构 Record<PathKeys<T>, any> & // 支持传统嵌套对象结构 { [K in keyof T & string]?: T[K] extends Record<string, any> ? FilterType<T[K]> : any }
通过交叉类型合并两种结构,既允许用点路径直接作为键,也支持嵌套的对象格式。
验证测试用例
用MyModel验证约束效果:
type FilterTypeForModel = FilterType<MyModel> // 合法输入(无TypeScript错误) const valid1: FilterTypeForModel = { string: 'foo' } const valid2: FilterTypeForModel = { subObject: { subObjString: 'foo'} } const valid3: FilterTypeForModel = { 'subObject.subObjString': 'foo' } // 非法输入(TypeScript会报错提示) const invalid1: FilterTypeForModel = { 'subObject.notExistingprop': 'foo' } const invalid2: FilterTypeForModel = { notExisting: 'foo' }
若要验证数组形式的路径列表,直接使用PathKeys<MyModel>即可:
const test1 = ['string', 'subObject'] satisfies PathKeys<MyModel>[] // 合法 const test2 = ['subObject.subObjString'] satisfies PathKeys<MyModel>[] // 合法 const test3 = ['subObject.notExistingProp'] satisfies PathKeys<MyModel>[] // 非法,TypeScript报错
内容的提问来源于stack exchange,提问作者TOPKAT
相关产品推荐
相关产品推荐

