TypeScript 从复杂对象构建联合类型
TypeScript build union type from complex object
我有以下 object
:
const object = {
root: {
path: 'path1',
},
paths: [
{
path: 'path2',
},
{
path: 'path3',
}
],
};
我想构建一个如下所示的联合类型:
type extractedPaths = 'path1' | 'path2' | 'path3';
有没有办法用 TypeScript 实现这个?
不确定你想在这里实现什么但是例如你可以有类似的东西(检查打字稿playground)
type ExtractedPath = 'path1' | 'path2' | 'path3'
type ExtractedObjectPath = {
path: ExtractedPath
}
type YourObject = {
root: {
path: ExtractedPath
},
paths: ExtractedObjectPath[]
}
const myObject: YourObject = {
root: {
path: 'path1',
},
paths: [
{
path: 'path2',
},
{
path: 'path3',
}
],
};
首先,您需要更改 object
的初始化,以便编译器知道它应该关心 literal types of the string
-valued properties. Otherwise you'll get a value of type {root:{path:string},paths:{path:string}[]}
and then no matter what you do you'll get just string
out. So let's use a const
assertion 以获得更具体的 object
类型:
const object = {
root: {
path: 'path1',
},
paths: [
{
path: 'path2',
},
{
path: 'path3',
}
],
} as const;
这给了我们以下类型:
/* const object: {
readonly root: { readonly path: "path1"; };
readonly paths: readonly [
{ readonly path: "path2"; },
{ readonly path: "path3"; }
];
} */
很好,编译器知道 "path1"
、"path2"
和 "path3"
。
然后您将需要确定如何识别您关心的类型。一种可能性是通过 object
的类型递归下降并构建一个 union of any string literal property value types you find along the way. Here's a recursive conditional type 来执行此操作:
type DeepStringLiteralValues<T> =
T extends string ? string extends T ? never : T :
T extends readonly any[] ? DeepStringLiteralValues<T[number]> :
T extends object ? { [K in keyof T]-?: DeepStringLiteralValues<T[K]> }[keyof T] : never;
对于任何类型 T
,如果 T
本身是字符串文字类型,那么 DeepStringLiteralValues<T>
将只是 T
。否则,如果它是类似数组的类型,我们会为数组的每个元素类型计算 DeepStringLiteralValues<T[number]>
。否则,如果它是类对象类型,我们会为 keyof T
中的每个键 K
收集 DeepStringLiteralValues<T[K]>
的并集。否则,它不是字符串或数组或对象,所以我们忽略它并 return never
。让我们看看它做了什么:
type ExtractedPaths = DeepStringLiteralValues<typeof object>
// type ExtractedPaths = "path1" | "path2" | "path3"
看起来不错。
也可以想象,您的意图只是使用名为 "path"
的键获得所有递归属性的并集。如果是这样,您可以改为这样做:
type DeepPropKey<T, K extends PropertyKey> =
K extends keyof T ? T[K] :
T extends readonly any[] ? DeepPropKey<T[number], K> :
T extends object ? { [P in keyof T]-?: DeepPropKey<T[P], K> }[keyof T] :
never;
它与其他版本类似,但 DeepPropKey<T, K>
试图为具有键 K
的属性查找 属性 值类型,而不是获取它可以找到的任何字符串文字。对于您的示例,它会产生相同的输出:
type ExtractedPaths = DeepPropKey<typeof object, "path">
// type ExtractedPaths = "path1" | "path2" | "path3"
我有以下 object
:
const object = {
root: {
path: 'path1',
},
paths: [
{
path: 'path2',
},
{
path: 'path3',
}
],
};
我想构建一个如下所示的联合类型:
type extractedPaths = 'path1' | 'path2' | 'path3';
有没有办法用 TypeScript 实现这个?
不确定你想在这里实现什么但是例如你可以有类似的东西(检查打字稿playground)
type ExtractedPath = 'path1' | 'path2' | 'path3'
type ExtractedObjectPath = {
path: ExtractedPath
}
type YourObject = {
root: {
path: ExtractedPath
},
paths: ExtractedObjectPath[]
}
const myObject: YourObject = {
root: {
path: 'path1',
},
paths: [
{
path: 'path2',
},
{
path: 'path3',
}
],
};
首先,您需要更改 object
的初始化,以便编译器知道它应该关心 literal types of the string
-valued properties. Otherwise you'll get a value of type {root:{path:string},paths:{path:string}[]}
and then no matter what you do you'll get just string
out. So let's use a const
assertion 以获得更具体的 object
类型:
const object = {
root: {
path: 'path1',
},
paths: [
{
path: 'path2',
},
{
path: 'path3',
}
],
} as const;
这给了我们以下类型:
/* const object: {
readonly root: { readonly path: "path1"; };
readonly paths: readonly [
{ readonly path: "path2"; },
{ readonly path: "path3"; }
];
} */
很好,编译器知道 "path1"
、"path2"
和 "path3"
。
然后您将需要确定如何识别您关心的类型。一种可能性是通过 object
的类型递归下降并构建一个 union of any string literal property value types you find along the way. Here's a recursive conditional type 来执行此操作:
type DeepStringLiteralValues<T> =
T extends string ? string extends T ? never : T :
T extends readonly any[] ? DeepStringLiteralValues<T[number]> :
T extends object ? { [K in keyof T]-?: DeepStringLiteralValues<T[K]> }[keyof T] : never;
对于任何类型 T
,如果 T
本身是字符串文字类型,那么 DeepStringLiteralValues<T>
将只是 T
。否则,如果它是类似数组的类型,我们会为数组的每个元素类型计算 DeepStringLiteralValues<T[number]>
。否则,如果它是类对象类型,我们会为 keyof T
中的每个键 K
收集 DeepStringLiteralValues<T[K]>
的并集。否则,它不是字符串或数组或对象,所以我们忽略它并 return never
。让我们看看它做了什么:
type ExtractedPaths = DeepStringLiteralValues<typeof object>
// type ExtractedPaths = "path1" | "path2" | "path3"
看起来不错。
也可以想象,您的意图只是使用名为 "path"
的键获得所有递归属性的并集。如果是这样,您可以改为这样做:
type DeepPropKey<T, K extends PropertyKey> =
K extends keyof T ? T[K] :
T extends readonly any[] ? DeepPropKey<T[number], K> :
T extends object ? { [P in keyof T]-?: DeepPropKey<T[P], K> }[keyof T] :
never;
它与其他版本类似,但 DeepPropKey<T, K>
试图为具有键 K
的属性查找 属性 值类型,而不是获取它可以找到的任何字符串文字。对于您的示例,它会产生相同的输出:
type ExtractedPaths = DeepPropKey<typeof object, "path">
// type ExtractedPaths = "path1" | "path2" | "path3"