Reputation:
I have the following object
:
const object = {
root: {
path: 'path1',
},
paths: [
{
path: 'path2',
},
{
path: 'path3',
}
],
};
and I would like to build a union type that would look like the following:
type extractedPaths = 'path1' | 'path2' | 'path3';
Is there a way to achieve this with TypeScript?
Upvotes: 1
Views: 646
Reputation: 328262
First, you will need to change your initialization of object
so that the compiler knows it's supposed to care about the 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 to get a more specific type for object
:
const object = {
root: {
path: 'path1',
},
paths: [
{
path: 'path2',
},
{
path: 'path3',
}
],
} as const;
That gives us the following type:
/* const object: {
readonly root: { readonly path: "path1"; };
readonly paths: readonly [
{ readonly path: "path2"; },
{ readonly path: "path3"; }
];
} */
Great, the compiler knows about "path1"
, "path2"
, and "path3"
.
Then you will need to determine how to identify the types you care about. One possibility is to recursively descend through the type of object
and build up a union of any string literal property value types you find along the way. Here's a recursive conditional type that does this:
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;
For any type T
, if T
is itself a string literal type, then DeepStringLiteralValues<T>
will just be T
. Otherwise, if it's an arraylike type, we evaluate DeepStringLiteralValues<T[number]>
for each element type of the array. Otherwise, if it's an objectlike type, we gather the union of DeepStringLiteralValues<T[K]>
for every key K
in keyof T
. Otherwise, it's not a string or an array or an object, so we ignore it and return never
. Let's see what that does:
type ExtractedPaths = DeepStringLiteralValues<typeof object>
// type ExtractedPaths = "path1" | "path2" | "path3"
Looks good.
It's also conceivable that your intent is to just get a union of all recursive properties with the key named "path"
. If so you can do this instead:
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;
It's similar to the other version, but DeepPropKey<T, K>
is trying to find property value types for properties with key K
, instead of grabbing any string literal it can find. For your example, it produces the same output, though:
type ExtractedPaths = DeepPropKey<typeof object, "path">
// type ExtractedPaths = "path1" | "path2" | "path3"
Upvotes: 1
Reputation: 9829
Not sure what you want to achieve here but for example you can have something like (check 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',
}
],
};
Upvotes: 0