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

如何实现支持指定嵌套路径的TypeScript递归可选化映射类型?

实现递归指定路径的部分可选映射类型MyRecursivePartializer

需求回顾

给定原始类型:

type Bar = {
    foo: string;
    prop: number;
    baz: {
        value: string;
        check: boolean;
    }
}

需要创建映射类型MyRecursivePartializer,支持指定嵌套路径将对应属性设为可选,且宽泛路径优先覆盖子路径:

  • 指定"foo"|"baz.check"时,仅foo和baz.check变为可选
  • 指定"foo"|"baz"时,foo和整个baz变为可选,且baz的所有子属性也递归可选

完整实现代码

// 拆分点分隔的路径为字符串数组
type SplitPath<T extends string> = T extends `${infer Head}.${infer Tail}` ? [Head, ...SplitPath<Tail>] : [T];

// 判断路径A是否被路径B覆盖(B是A的父路径)
type IsPathCovered<T extends string, U extends string> = 
  T extends `${U}.${infer _}` ? true : 
  U extends `${T}.${infer _}` ? false : 
  false;

// 过滤掉被其他宽泛路径覆盖的子路径
type FilterCoveredPaths<T extends string> = T extends any ? 
  Exclude<T, { [K in T]: IsPathCovered<K, Exclude<T, K>> extends true ? K : never }[T]> : 
  never;

// 核心递归部分可选类型
type MyRecursivePartializer<T, Paths extends string> = 
  FilterCoveredPaths<Paths> extends infer FilteredPaths extends string ?
  {
    [K in keyof T]: 
      K extends FilteredPaths ? 
        // 当前属性匹配顶级路径,设为可选并递归Partial子属性
        Partial<T[K]> | undefined :
        // 检查是否存在以当前属性开头的子路径,递归处理
        (FilteredPaths extends `${K}.${infer SubPath}` ? 
          MyRecursivePartializer<T[K], SubPath> :
          // 无匹配路径,保持原类型
          T[K]
        )
  } : never;

验证示例

示例1:指定"foo"|"baz.check"

type PartiallyBar = MyRecursivePartializer<Bar, "foo"|"baz.check">;
// 生成的类型结构:
// {
//   foo?: string | undefined;
//   prop: number;
//   baz: {
//     value: string;
//     check?: boolean | undefined;
//   };
// }

示例2:指定"foo"|"baz"

type PartiallyBar2 = MyRecursivePartializer<Bar, "foo"|"baz">;
// 生成的类型结构:
// {
//   foo?: string | undefined;
//   prop: number;
//   baz?: {
//     value?: string | undefined;
//     check?: boolean | undefined;
//   } | undefined;
// }

逻辑说明

  1. 路径拆分与冲突处理:

    • SplitPath将嵌套路径拆分为数组,方便递归遍历嵌套属性。
    • FilterCoveredPaths自动过滤被父路径覆盖的子路径,比如同时指定"baz"和"baz.check"时,会保留"baz"并丢弃"baz.check",确保宽泛路径优先生效。
  2. 递归属性处理:

    • 遍历原类型的每个属性,若属性名直接匹配过滤后的路径,将其设为可选并递归将子属性全部设为可选(通过Partial实现)。
    • 若属性是嵌套对象且存在对应子路径,则递归进入嵌套对象处理指定子路径。
    • 未匹配路径的属性保持原类型不变。

现成库方案

社区工具库type-fest提供了SetOptional(实现顶级属性可选)和DeepPartial(全递归可选),但没有直接支持指定嵌套路径+优先级覆盖的现成工具。你可以基于type-fest的基础类型扩展实现上述逻辑,或者直接使用上面的自定义类型。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 15:52:50