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

如何从不同类型的树形结构中递归提取公共字段?

如何从不同类型的树形结构中递归提取公共字段?

我完全懂你的困扰——你想从这个由Zod定义的多变体树形结构里,递归提取所有节点的id和children字段,方便写ID唯一性检查的函数和测试,但普通的Pick只能处理顶层类型,子节点还是带着原来的所有专属必填字段,导致手动创建的简化节点类型报错。别急,咱们写个递归版的Pick工具类型就能解决这个问题!

问题根源拆解

你现在用Pick<LeadingItem, 'id' | 'children'>,只会把顶层节点的类型精简成只含id和children,但children的类型还是原来的联合类型(followingFoo/followingBar/followingBaz的推断类型),这些子类型都要求必须有各自的专属字段(比如followingBazField),所以你手动创建的只带id和children的子节点就会触发报错——因为缺少那些必填的专属字段。

解决方案:递归Pick工具类型

我们可以定义一个RecursivePick类型,它会递归地对嵌套的children数组也应用Pick操作,把所有层级的节点都精简成你需要的结构:

type RecursivePick<T, K extends keyof T> = T extends unknown
  ? {
      [P in K]: P extends 'children'
        ? RecursivePick<T[P][number], K>[]
        : T[P]
    }
  : never;

这个类型的核心逻辑:

  • 利用T extends unknown触发联合类型的自动分发,确保每个变体类型都被单独处理
  • 只保留你指定的键K
  • 如果当前处理的键是children,就对数组里的每一项递归应用RecursivePick,再重新组成数组
  • 其他键直接保留原类型

结合你的代码实战

现在把这个工具类型用到你的代码里,就能生成符合预期的ItemWithIDAndChildrenOnly:

import { z } from 'zod'

const followingFooSchema = z.object({
  id: z.string(),
  get children() { return z.array(followingFooSchema) },
  followingFooField: z.string()
});

const followingBarSchema = z.object({
  id: z.string(),
  get children() { return z.array(z.union([followingFooSchema, followingBarSchema])) },
  followingBarField: z.boolean()
});

const followingBazSchema = z.object({
  id: z.string(),
  get children() { return z.array(z.union([followingFooSchema, followingBarSchema, followingBazSchema])) },
  followingBazField: z.number()
});

const leadingFooSchema = z.object({
  id: z.string(),
  children: z.array(followingFooSchema),
  leadingFooField: z.string()
});

const leadingBarSchema = z.object({
  id: z.string(),
  children: z.array(z.union([followingFooSchema, followingBarSchema, followingBazSchema])),
  leadingBarField: z.boolean()
});

const leadingItemSchema = z.union([leadingFooSchema, leadingBarSchema]);

type LeadingItem = z.infer<typeof leadingItemSchema>;
// 使用递归Pick工具类型生成目标类型
type ItemWithIDAndChildrenOnly = RecursivePick<LeadingItem, 'id' | 'children'>;

// 现在这个赋值完全不会报错了!
const item: ItemWithIDAndChildrenOnly = {
  id: "",
  children: [{ id: "", children: [] }]
};

效果验证

现在ItemWithIDAndChildrenOnly会递归地把所有层级的节点都统一成{ id: string; children: ItemWithIDAndChildrenOnly[] }结构,不管原来的节点是Foo、Bar还是Baz类型,完全符合你想要的递归接口,写ID唯一性检查的函数和测试就轻松多了。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 07:13:01