深层嵌套对象、数组和对象数组的点表示法键提取

问题描述 投票:0回答:2

我想提取所有深度嵌套的键,包括数组中的键,但不包括中间索引访问:

type Pack = {
  name: string
  items: string[]
}

interface Params {
  name: string
  items: string[]
  pack: Pack
  packs: Pack[]
  nested1: {
    nested2: {
      nested3: string
    }[]
  }
}

type ParamKeys = <...> // expected: 'name' | 'items' | 'pack' | 'pack.name' | 'pack.items'| 'packs' | 'packs.name' | 'packs.items' | 'nested1' | 'nested1.nested2'| 'nested1.nested2.nested3'

我知道这个问题已经被多次回答,但所有解决方案都需要数组索引,例如

packs.8.name
,因为密钥被推断为
packs.${number}.name
。就我而言,我希望用户构造一条适用于所有数组元素的选项记录,因此索引并不是真正有用,并且将数组中的嵌套元素作为必需的键排除在外,这不是我想要的。另一个不太理想的解决方案是,如果上述方法不可行,则将索引显式输入为零,即
nested1.nested2.0.nested3
作为必需键,例如

typescript types nested
2个回答
4
投票

算法:

  • 如果
    T
    是某种我们不想通过 return 更深入的类型
    ''
  • Else if
    T
    是一个数组,调用
    T
  • 元素的类型
  • 否则通过
    T
    的字符串键进行映射,其中
    K
    T
    的键:
    • 如果
      T[K]
      是某种我们不想通过 return 更深入的类型
      K
    • Else 如果
      T[K]
      是我们想要忽略的某种类型,则返回
      never
    • 否则返回
      K
      的并集,并对
      T[K]
      进行递归检查,前缀为
      ${K}.
      (假设在这一步我们只处理对象)
  • 获取映射类型的值

实施:

首先,我们要停止的类型,主要是原语:

type StopTypes = number | string | boolean | symbol | bigint | Date;

要忽略的类型:

type ExcludedTypes = (...args: any[]) => any;

允许我们创建

.
符号的类型。如果第二个参数是空字符串,则返回第一个参数,不带尾随
.
:

type Dot<T extends string, U extends string> = '' extends U ? T : `${T}.${U}`;
type GetKeys<T> = T extends StopTypes
    ? ''
    : T extends readonly unknown[]
    ? GetKeys<T[number]>
    : {
        [K in keyof T & string]: T[K] extends StopTypes
        ? K
        : T[K] extends ExcludedTypes
        ? never
        : K | Dot<K, GetKeys<T[K]>>;
    }[keyof T & string];

我们使用索引访问来获取数组类型元素的类型,并使用映射类型来映射对象的键

测试:

type Pack = {
    name: string;
    items: string[];
};

interface Params {
    name: string;
    items: string[];
    pack: Pack;
    packs: Pack[];
    fn: (...args: any) => any;
    nested1: {
        nested2: {
            nested3: string;
        }[];
    };
}
// "name" | "items" | "pack" | "packs" | "nested1" | "pack.name" | "pack.items" | "packs.name" | "packs.items" | "nested1.nested2" | "nested1.nested2.nested3"
type ParamKeys = GetKeys<Params>;

游乐场


0
投票

基于 @wonderflame 的回答,如果我们有一个更简单的用例,不需要嵌套数组(例如,跳过

packs.name
,因为 packs 是一个数组),这可能是一个解决方案。

type Dot<T extends string, U extends string> = U extends '' ? T : `${T}.${U}`;

type NestedKeysOf<T> = T extends Array<any> 
  ? never
  : T extends Record<string, any>
    ? {
      [K in keyof T & string]: K | Dot<K, NestedKeysOf<T[K]>>;
    }[keyof T & string]
    : '';

这将导致以下字段:

"name" | "items" | "pack" | "packs" | "nested1" | "pack.name" | "pack.items" | "nested1.nested2"
© www.soinside.com 2019 - 2024. All rights reserved.