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

如何在TypeScript中选取嵌套字段并保留可选性与可空性?

解决TypeScript嵌套Pick保留字段可选性与可空性的问题

问题背景

我遇到的问题和嵌套Pick类型的需求类似,但更复杂——需要保留字段的null值与可选性。场景如下:

  • 有一个配送页面的GraphQL查询,以及GraphQL端点生成的全量接口文件(比如GQLCountry)
  • 不能直接使用完整接口,因为其中包含未查询的字段
  • 需要基于生成的类型得到指定的ShippingDataResponse接口
  • 现有Pick工具类型的方案会将address和country重新声明为非可选、非可空字段,导致类型错误
  • 手动修改4000行的生成文件不现实,且文件可能随时变更,需要让类型始终与GraphQL输出保持同步

解决方案

我们可以自定义递归的工具类型,既能嵌套选择指定字段,又能完整保留原类型的可选性、可空性等特性。

自定义递归Pick工具类型

type DeepPick<T, K extends string> = K extends `${infer Key}.${infer Rest}`
  ? {
      [P in Key]: T[P] extends infer U
        ? U extends null | undefined
          ? U | (U extends object ? DeepPick<NonNullable<U>, Rest> : never)
          : U extends object
          ? DeepPick<U, Rest>
          : never
        : never
    }
  : {
      [P in K]: T[P]
    };

// 组合多个DeepPick结果的工具类型
type DeepPickMultiple<T, K extends string[]> = K extends [infer First, ...infer Rest]
  ? First extends string
    ? Rest extends string[]
      ? DeepPick<T, First> & DeepPickMultiple<T, Rest>
      : never
    : never
  : {};

用法示例

假设全量GraphQL生成类型如下:

interface GQLUser {
  id: string;
  name?: string | null;
  address?: {
    street: string;
    city?: string | null;
    country?: {
      code: string;
      name?: string | null;
    } | null;
  } | null;
}

要生成只包含id、name、address.city、address.country.code的ShippingDataResponse:

type ShippingDataResponse = DeepPickMultiple<GQLUser, [
  'id',
  'name',
  'address.city',
  'address.country.code'
]>;

生成的类型会完整保留原字段特性:

  • name仍为string | null | undefined
  • address仍为{ city?: string | null; country?: { code: string } | null } | null | undefined
  • address.city保持原有的string | null | undefined
  • address.country.code为必填string(继承原类型的必填属性)

关键特性说明

  • 递归解析路径:通过模板字符串类型拆分嵌套字段路径(如address.city拆分为address和city)
  • 保留可空性:判断原字段是否为null或undefined,生成新类型时完整保留这些特性
  • 保留可选性:通过映射类型[P in Key]自动继承原字段的可选标记(?)
  • 支持多字段选择:DeepPickMultiple可组合多个嵌套路径的选择结果

额外优化

如果需要支持嵌套排除字段(类似Omit的嵌套版本),可基于同样思路扩展:

type DeepOmit<T, K extends string> = K extends `${infer Key}.${infer Rest}`
  ? {
      [P in keyof T]: P extends Key
        ? T[P] extends infer U
          ? U extends null | undefined
            ? U | (U extends object ? DeepOmit<NonNullable<U>, Rest> : never)
            : U extends object
            ? DeepOmit<U, Rest>
            : never
          : never
        : T[P]
    }
  : Omit<T, K>;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 05:12:41