gpt4 book ai didi

typescript :嵌套对象的深度键

转载 作者:行者123 更新时间:2023-12-01 04:35:50 24 4
gpt4 key购买 nike

所以我想找到一种方法来拥有嵌套对象的所有键。

我有一个在参数中采用类型的泛型类型。我的目标是获取给定类型的所有键。

在这种情况下,以下代码运行良好。但是当我开始使用嵌套对象时,情况就不同了。

type SimpleObjectType = {
a: string;
b: string;
};

// works well for a simple object
type MyGenericType<T extends object> = {
keys: Array<keyof T>;
};

const test: MyGenericType<SimpleObjectType> = {
keys: ['a'];
}

这是我想要实现的目标,但它不起作用。

type NestedObjectType = {
a: string;
b: string;
nest: {
c: string;
};
otherNest: {
c: string;
};
};

type MyGenericType<T extends object> = {
keys: Array<keyof T>;
};

// won't works => Type 'string' is not assignable to type 'a' | 'b' | 'nest' | 'otherNest'
const test: MyGenericType<NestedObjectType> = {
keys: ['a', 'nest.c'];
}

那么在不使用函数的情况下,我该怎么做才能将这种 key 提供给 test ?

最佳答案

TS4.1 更新 现在可以在类型级别连接字符串文字,使用在 microsoft/TypeScript#40336 中实现的模板文字类型.可以调整下面的实现来使用它而不是像 Cons 这样的东西。 (它本身可以使用可变元组类型实现为 introduced in TypeScript 4.0 ):

type Join<K, P> = K extends string | number ?
P extends string | number ?
`${K}${"" extends P ? "" : "."}${P}`
: never : never;
这里 Join连接两个中间有一个点的字符串,除非最后一个字符串为空。所以 Join<"a","b.c">"a.b.c"Join<"a","">"a" .
然后 PathsLeaves变得:
type Paths<T, D extends number = 10> = [D] extends [never] ? never : T extends object ?
{ [K in keyof T]-?: K extends string | number ?
`${K}` | Join<K, Paths<T[K], Prev[D]>>
: never
}[keyof T] : ""

type Leaves<T, D extends number = 10> = [D] extends [never] ? never : T extends object ?
{ [K in keyof T]-?: Join<K, Leaves<T[K], Prev[D]>> }[keyof T] : "";
其他类型不属于它:
type NestedObjectPaths = Paths<NestedObjectType>;
// type NestedObjectPaths = "a" | "b" | "nest" | "otherNest" | "nest.c" | "otherNest.c"
type NestedObjectLeaves = Leaves<NestedObjectType>
// type NestedObjectLeaves = "a" | "b" | "nest.c" | "otherNest.c"
type MyGenericType<T extends object> = {
keys: Array<Paths<T>>;
};

const test: MyGenericType<NestedObjectType> = {
keys: ["a", "nest.c"]
}
其余答案基本相同。 TS4.1 也将支持递归条件类型(如在 microsoft/TypeScript#40002 中实现),但递归限制仍然适用,因此您会遇到没有深度限制器(如 Prev)的树状结构问题。 .
请注意,这将使非点式键的点路径,如 {foo: [{"bar-baz": 1}]}可能会产生 foo.0.bar-baz .所以要小心避免这样的键,或者重写上面的以排除它们。
另请注意:这些递归类型本质上是“棘手的”,如果稍作修改,往往会使编译器不满意。如果你不走运,你会看到诸如“类型实例化太深”之类的错误,如果你很不走运,你会看到编译器耗尽了你所有的 CPU 并且永远不会完成类型检查。总的来说,我不知道对这类问题该说什么……只是这些事情有时比它们的值(value)更麻烦。
Playground link to code

TS4.1 之前的答案:
如前所述,目前无法在类型级别连接字符串文字。有一些建议可能允许这样做,例如 a suggestion to allow augmenting keys during mapped typesa suggestion to validate string literals via regular expression ,但目前这是不可能的。
您可以将路径表示为 tuples,而不是将路径表示为虚线字符串。字符串文字。所以 "a"变成 ["a"] , 和 "nest.c"变成 ["nest", "c"] .在运行时很容易通过 split() 在这些类型之间进行转换。和 join()方法。

所以你可能想要类似 Paths<T> 的东西返回给定类型的所有路径的联合 T ,或者可能 Leaves<T>这只是 Paths<T> 的那些元素指向非对象类型本身。没有对这种类型的内置支持; ts-toolbelt图书馆 has this ,但由于我无法在 Playground 中使用该库,我会在这里推出自己的。
请注意: PathsLeaves本质上是递归的,这可能会给编译器带来很大的负担。和 recursive types of the sort needed for thisnot officially supported在 TypeScript 中。我将在下面介绍的是以这种不确定/并非真正支持的方式递归的,但我尝试为您提供一种指定最大递归深度的方法。
开始了:
type Cons<H, T> = T extends readonly any[] ?
((h: H, ...t: T) => void) extends ((...r: infer R) => void) ? R : never
: never;

type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...0[]]

type Paths<T, D extends number = 10> = [D] extends [never] ? never : T extends object ?
{ [K in keyof T]-?: [K] | (Paths<T[K], Prev[D]> extends infer P ?
P extends [] ? never : Cons<K, P> : never
) }[keyof T]
: [];


type Leaves<T, D extends number = 10> = [D] extends [never] ? never : T extends object ?
{ [K in keyof T]-?: Cons<K, Leaves<T[K], Prev[D]>> }[keyof T]
: [];
Cons<H, T>的意图是走任何类型 H和一个元组类型 T并使用 H 生成一个新元组前置到 T .所以 Cons<1, [2,3,4]>应该是 [1,2,3,4] .实现使用 rest/spread tuples .我们需要这个来建立路径。
型号 Prev是一个长元组,您可以使用它来获取前一个数字(最多可达最大值)。所以 Prev[10]9 , 和 Prev[1]0 .当我们深入对象树时,我们将需要它来限制递归。
最后, Paths<T, D>Leaves<T, D>通过深入到每个对象类型来实现 T和收集 key ,以及 Cons将它们放到 Paths 上和 Leaves这些键的属性。它们的区别在于 Paths还直接包括联合中的子路径。默认情况下,深度参数 D10 ,并且在每一步向下我们减少 D一个,直到我们尝试过去 0 ,此时我们停止递归。

好的,让我们测试一下:
type NestedObjectPaths = Paths<NestedObjectType>;
// type NestedObjectPaths = [] | ["a"] | ["b"] | ["c"] |
// ["nest"] | ["nest", "c"] | ["otherNest"] | ["otherNest", "c"]
type NestedObjectLeaves = Leaves<NestedObjectType>
// type NestedObjectLeaves = ["a"] | ["b"] | ["nest", "c"] | ["otherNest", "c"]
为了查看深度限制的用处,假设我们有一个这样的树类型:
interface Tree {
left: Tree,
right: Tree,
data: string
}
那么, Leaves<Tree>是,呃,大:
type TreeLeaves = Leaves<Tree>; // sorry, compiler 💻⌛😫
// type TreeLeaves = ["data"] | ["left", "data"] | ["right", "data"] |
// ["left", "left", "data"] | ["left", "right", "data"] |
// ["right", "left", "data"] | ["right", "right", "data"] |
// ["left", "left", "left", "data"] | ... 2038 more ... | [...]
并且编译器生成它需要很长时间,并且您的编辑器的性能会突然变得非常非常差。让我们将其限制为更易于管理的内容:
type TreeLeaves = Leaves<Tree, 3>;
// type TreeLeaves2 = ["data"] | ["left", "data"] | ["right", "data"] |
// ["left", "left", "data"] | ["left", "right", "data"] |
// ["right", "left", "data"] | ["right", "right", "data"]
这迫使编译器停止查看 3 的深度,因此所有路径的长度最多为 3。

所以,这有效。很可能 ts-toolbelt 或其他一些实现可能会更加小心,以免导致编译器心脏病发作。所以我不一定会说你应该在没有大量测试的情况下在你的生产代码中使用它。
但无论如何,这里是您想要的类型,假设您拥有并想要 Paths :
type MyGenericType<T extends object> = {
keys: Array<Paths<T>>;
};

const test: MyGenericType<NestedObjectType> = {
keys: [['a'], ['nest', 'c']]
}
希望有所帮助;祝你好运!
Link to code

关于 typescript :嵌套对象的深度键,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58434389/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com