Issue
Given a two level nested type (never more, never less levels):
export type SomeNested = {
someProp: {
someChild: string
someOtherChild: string
}
someOtherProp: {
someMoreChildren: string
whatever: string
else: string
}
}
I want to have the following union type:
"someProp.someChild" | "someProp.someOtherChild" | "someOtherProp.someMoreChildren" | "someOtherProp.whatever" | "someOtherProp.else"
.
I have tried something along the lines of
type FirstLevel = keyof SomeNested
type SecondLevel = SomeNested[FirstLevel]
type Dot = `${FirstLevel}.${SecondLevel}`
but I am probably missing some condition. Could somebody point me in the right direction?
Thanks!
Solution
Note, this question is asking specifically to get the union of dotted paths for an object type that consists of exactly two levels. For those interested in the union of dotted paths for an object of arbitrary depth, you should look at this question instead.
By indexing into SomeNested
with the union of its known keys type FirstLevel = keyof SomeNested
, you are getting a union of the known property types:
type X = SomeNested[FirstLevel];
/* type X = {
someChild: string;
someOtherChild: string;
} | {
someMoreChildren: string;
whatever: string;
else: string;
} */
And the keys of that union would only be the keys that appear in all members of that union (which would be nothing at all, for your example):
type SecondLevel = keyof SomeNested[FirstLevel] // never
Essentially by making these unions you have thrown away information about the correspondence between specific keys of SomeNested
and their specific property types. To fix it you will need to essentially iterate through the keys of SomeNested
and look at the subproperties for each key separately.
One way to do this is with a mapped type, which lets you iterate over keys and then make a property for each key. For example:
type Mapped = { [K in keyof SomeNested]:
`${K}.${Extract<keyof SomeNested[K], string>}`
}
/* type Mapped = {
someProp: "someProp.someChild" | "someProp.someOtherChild";
someOtherProp: "someOtherProp.someMoreChildren" |
"someOtherProp.whatever" | "someOtherProp.else";
} */
Each property of Mapped
is a template literal type which starts with a particular key K
from keyof SomeNested
, followed by a dot, and then followed by the union of string keys from the corresponding property SomeNested[K]
. (Ideally you could just write ${keyof SomeNested[K]}
, but the compiler does not realize that these will definitely be strings for all possible K
, so we can use the Extract<T, U>
utility type to convince the compiler that we're only looking at string
-compatible keys).
Of course you don't really want Mapped
, an object type with the same keys as SomeNested
. But you do want the union of its property types, so now you can index into it with the union of its keys:
type Dot = { [K in keyof SomeNested]:
`${K}.${Extract<keyof SomeNested[K], string>}`
}[keyof SomeNested]
/* type Dot = "someProp.someChild" | "someProp.someOtherChild" |
"someOtherProp.someMoreChildren" | "someOtherProp.whatever" | "someOtherProp.else" */
And there you go!
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.