TypeScript使用mapped types实现投影时如何禁止传入额外未知属性
解决方案
核心思路是新增递归的严格投影校验类型,对传入的投影对象的所有key做合法性校验,非法key直接标记为never触发编译报错,同时不破坏原有类型推断逻辑。
首先保留原有已实现的ProjectionMap和Projection类型,新增StrictValidateProjection类型用于递归校验多余key:
type StrictValidateProjection<T, P> = { [K in keyof P]: K extends keyof T // 合法key如果对应嵌套对象,递归校验子级 ? T[K] extends object ? P[K] extends object ? StrictValidateProjection<T[K], P[K]> : P[K] : P[K] // 非法key直接标记为never,触发类型错误 : never }
然后修改magicalFunction的参数类型,增加校验逻辑:
function magicalFunction<P extends ProjectionMap<SomeType>>( // 只有当P完全通过校验时,参数类型才为P,否则为never p: P extends StrictValidateProjection<SomeType, P> ? P : never ): Projection<SomeType, P> { /* 原有运行时逻辑 */ throw new Error("WIP") }
效果验证
- 合法调用完全保留原有类型推断能力:
const res1 = magicalFunction({ a: 1 }) // 类型:{ a: string } const res2 = magicalFunction({ b: 1 }) // 类型:{ b: { a: string, b: string } } const res3 = magicalFunction({ b: { a: 1 } }) // 类型:{ b: { a: string } }
- 非法传入多余key直接触发编译错误:
// 根级多余key报错 magicalFunction({ a: 1, c: 1 }) // 报错提示:不能将类型“number”分配给类型“never” // 嵌套级多余key同样报错 magicalFunction({ b: { a: 1, c: 1 } }) // 报错提示:不能将类型“number”分配给类型“never”
适配Repository场景的完整实现
针对Mongo查询Repository类的最终需求,完整的类型定义如下:
export type ProjectionMap<T> = { [k in keyof T]?: T[k] extends object ? 1 | ProjectionMap<T[k]> : 1 } export type Projection<T, P extends ProjectionMap<T>> = { [k in keyof T & keyof P]: P[k] extends object ? Projection<T[k], P[k]> : T[k] } type StrictValidateProjection<T, P> = { [K in keyof P]: K extends keyof T ? T[K] extends object ? P[K] extends object ? StrictValidateProjection<T[K], P[K]> : P[K] : P[K] : never } class Repo<T> { async find< Query = unknown, P extends ProjectionMap<T> = ProjectionMap<T> >( query: Query, options?: { projection?: P extends StrictValidateProjection<T, P> ? P : never } ): Promise<Projection<T, P>> { // 此处实现Mongo查询运行时逻辑 throw new Error("WIP") } }
使用效果完全符合预期:
class TestClass { num: number str: string nested: { foo: string, bar: number } } const repo = new Repo<TestClass>() // 正常查询,返回值类型与投影匹配 const res = await repo.find({}, { projection: { num: 1, nested: { foo: 1 } } }) // res类型:{ num: number, nested: { foo: string } } // 传入不存在的字段直接报错 await repo.find({}, { projection: { notExist: 1 } }) // 编译报错
内容的提问来源于stack exchange,提问作者Ridiculous
相关产品推荐
相关产品推荐

