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

如何在TypeScript中通过类型推断转换对象字段名?

解决TypeScript中解析器生成器的对象键类型转换问题

我实现了一个能转换对象键的解析器生成器(JavaScript中运行正常),但在TypeScript里无法正确推断转换后的对象键类型。

问题场景

示例代码:

let sampleData = {
  id: 1,
  delete_time$optional: new Date()
}
let parser = inferFromSampleValue(sampleData)

期望构造出的解析器要求输入对象包含必填数字类型id和可选日期类型delete_time。

初始实现的问题

初始类型定义:

type Parser<T> = {
  // 类型不匹配时抛出运行时错误
  parse(input: unknown): T
  // 预期数据的TypeScript类型字符串描述
  type: string
}
function inferFromSampleValue<T>(value: T): Parser<T> {
  // 实现细节省略
}

此时解析器的类型签名为:

{
  id: number
  delete_time$optional: Date
}

与预期的类型签名不符:

{
  id: number
  delete_time?: Date
}

尝试过的方案及问题

我尝试编写了以下泛型类型,但结果不符合预期:

type InferFieldName<S> = S extends `${infer N}$optional` ? N : S

type ExcludeOptionalFieldName<S> = S extends `${infer N}$optional` ? never : S

type InferType<T> = T extends Record<infer K extends keyof T, infer V>
  ? Partial<Record<InferFieldName<K>, V>> &
      Pick<T, ExcludeOptionalFieldName<keyof T>>
  : T

function inferFromSampleValue<T>(value: T): Parser<InferType<T>> {
  // 实现细节省略
}

此时解析器的类型签名变成了:

{
  id: number
  delete_time?: number | Date
}

可选字段的类型被错误混合成number | Date,而非预期的Date。

我还尝试过这种写法,但TypeScript不支持该语法:

type InferType<T> = T extends {}
  ? {
      [InferFieldName<P> where P in keyof T]: T[P]
    }
  : T

注:实际代码包含更多类型转换(如可空字段、枚举)且支持递归处理,上述为简化版本。

解决方案

要正确实现键转换和类型保留,需使用映射类型结合条件类型分别处理必填和可选字段,避免类型交叉导致的混合问题。

正确的泛型类型定义

type InferFieldName<S extends string> = S extends `${infer N}$optional` ? N : S

// 提取所有带$optional后缀的键
type OptionalKeys<T> = {
  [K in keyof T]: K extends `${string}$optional` ? K : never
}[keyof T]

// 提取所有不带$optional后缀的键
type RequiredKeys<T> = Exclude<keyof T, OptionalKeys<T>>

type InferType<T> = 
  // 处理必填字段:直接保留键和类型
  { [K in RequiredKeys<T>]: T[K] } &
  // 处理可选字段:移除$optional后缀并设为可选
  { [K in OptionalKeys<T> as InferFieldName<K>]?: T[K] } extends infer O
  // 将交叉类型展开为普通对象类型,提升可读性
  ? { [P in keyof O]: O[P] }
  : never

type Parser<T> = {
  parse(input: unknown): T
  type: string
}

function inferFromSampleValue<T>(value: T): Parser<InferType<T>> {
  // 实现细节省略
}

测试效果

let sampleData = {
  id: 1,
  delete_time$optional: new Date()
}
let parser = inferFromSampleValue(sampleData)
// parser的类型为 Parser<{ id: number; delete_time?: Date | undefined }>

递归支持(针对嵌套对象)

如果需要处理嵌套对象中的$optional字段,可以修改InferType为递归类型:

type InferType<T> = 
  T extends object
    ? {
        [K in keyof T]: InferType<T[K]>
      } extends infer Obj
      ? {
          [K in RequiredKeys<Obj>]: Obj[K]
        } & {
          [K in OptionalKeys<Obj> as InferFieldName<K>]?: Obj[K]
        } extends infer Final
        ? { [P in keyof Final]: Final[P] }
        : never
      : never
    : T

这样就能正确处理嵌套结构中的键转换和类型推断了。

内容的提问来源于stack exchange,提问作者Beeno Tung

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 14:44:53