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

如何基于递归对象结构用TypeScript推导嵌套类型?

基于递归Item对象推导嵌套TypeScript类型的解决方案

问题背景

给定如下递归定义的TypeScript对象结构:

interface Item {
  name: string;
  type: 'string' | 'url' | 'list' | 'file',
  require?: boolean;
  allowedFileTypes: string[];
  subFields: Item[];
}

const items = {
  name: "navigation",
  type: "list",
  subFields: [
    {
      name: "label",
      type: "string",
      defaultValue: "",
      require: true,
    },
    {
      name: "url",
      type: "url",
      defaultValue: "",
      require: true,
    },
    {
      name: "images",
      type: "list",
      subFields: [
        {
          name: "label",
          type: "string",
          defaultValue: "",
          require: true,
        },
        {
          name: "icon",
          type: "file",
          allowedFileTypes: ["png", "svg"],
        },
      ],
    },
  ],
};

需要从该结构推导得到如下嵌套类型:

// 预期输出:
type Items = {
  navigation: Array<{
    label: string;
    url: string;
    images: Array<{
      label: string;
      icon: 'png' | 'svg';
    }>
  }>
}

此前已完成扁平结构的推导,但递归嵌套部分遇到瓶颈,需解决:

  • 利用typeof结合泛型递归推导嵌套类型
  • 根据require标记处理可选属性
  • 将file类型的allowedFileTypes转换为字符串联合类型
  • 确认该需求是否在TypeScript能力范围内

解决方案

该需求完全在TypeScript的能力范围内,可通过递归条件类型结合typeof实现。核心思路是针对不同type字段做分支处理,递归解析subFields,并根据require控制属性可选性。

实现代码

// 给items添加as const断言,保留字面量类型以便精确推导
const items = {
  name: "navigation",
  type: "list",
  subFields: [
    {
      name: "label",
      type: "string",
      defaultValue: "",
      require: true,
    },
    {
      name: "url",
      type: "url",
      defaultValue: "",
      require: true,
    },
    {
      name: "images",
      type: "list",
      subFields: [
        {
          name: "label",
          type: "string",
          defaultValue: "",
          require: true,
        },
        {
          name: "icon",
          type: "file",
          allowedFileTypes: ["png", "svg"],
        },
      ],
    },
  ],
} as const;

// 递归类型转换器:将Item结构映射为目标类型
type ItemToType<T extends typeof items | typeof items['subFields'][number]> = 
  // 处理string/url类型,直接映射为string
  T extends { type: 'string' | 'url' } 
    ? string 
    // 处理file类型,提取allowedFileTypes的字面量联合类型
    : T extends { type: 'file'; allowedFileTypes: readonly string[] }
      ? T['allowedFileTypes'][number]
      // 处理list类型,递归生成数组对象结构
      : T extends { type: 'list'; subFields: readonly any[] }
        ? Array<
            // 先处理必填属性
            {
              [K in T['subFields'][number]['name'] as Extract<T['subFields'][number], { name: K, require: true }>['name']]: 
                ItemToType<Extract<T['subFields'][number], { name: K }>> 
            } & 
            // 再处理可选属性
            Partial<{
              [K in T['subFields'][number]['name'] as Extract<T['subFields'][number], { name: K, require?: false }>['name']]: 
                ItemToType<Extract<T['subFields'][number], { name: K }>>
            }>
          >
        : never;

// 生成最终目标类型
type Items = {
  [K in typeof items['name']]: ItemToType<typeof items>
};

关键说明

  1. as const断言:强制TypeScript保留对象中字符串、数组的字面量类型(比如将["png", "svg"]识别为readonly ["png", "svg"],而非string[]),这是生成精确联合类型的前提。
  2. 递归条件分支:
    • 对string/url类型直接映射为string;
    • 对file类型提取allowedFileTypes的元素类型作为联合类型;
    • 对list类型递归解析subFields,生成对应的数组嵌套结构;
  3. 可选属性处理:通过Extract分别筛选出require: true的必填字段和require为false或未定义的可选字段,再通过Partial标记可选性;
  4. 最终类型生成:以根节点的name为键,调用递归类型转换器得到完整嵌套类型。

验证结果

上述代码会生成完全符合预期的类型:

type Items = {
    navigation: Array<{
        label: string;
        url: string;
        images: Array<{
            label: string;
            icon: "png" | "svg";
        }>;
    }>;
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 19:54:27