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

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")
}

效果验证

  1. 合法调用完全保留原有类型推断能力:
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 } }
  1. 非法传入多余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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 02:06:03