I want to create a type
from the property values of an class.
class Names {
parents = {
mother: "Susan",
dad: "Mike"
}
children ={
son: "Peter"
}
}
// Result should be a type which is like this: "Susan" | "Mike" | "Peter"
type NamesICanUse = ...
How can I achieve this?
CodePudding user response:
In your original definition of Names
, the compiler inferred the type of parents
as {mother: string, dad: string}
and the type of children
as {son: string}
. It's forgotten about "Susan"
, "Mike",
and "Peter"
entirely. The language assumes that you might want to support changing the properties to other strings, and so it doesn't keep track of the specific string values you initialized the object with. That means any type manipulation you do would produce string
for NamesICanUse
, which isn't what you're looking for.
So first you need to tell the compiler that you want those properties to have string literal types corresponding to the specific values you assigned. One way to do this is to put const
assertions on your object literals. This expresses the intent that the values won't change at all, and so you'd like the type to be as narrow as possible:
export class Names {
parents = {
mother: "Susan",
dad: "Mike"
} as const // <--
children = {
son: "Peter"
} as const // <--
}
Now if you inspect the parents
property you'll see its type is {readonly mother: "Susan"; readonly dad: "Mike"}
, and the children
property has type {readonly son: "Peter";}
. And so we can proceed.
If you have an object type T
and you want to get a union of all its known property types, you can index into it with keyof T
, where the keyof
type operator gives a union of known keys. That's T[keyof T]
. For example:
interface Foo {
x: 0,
y: "a",
z: true
}
type FooVals = Foo[keyof Foo];
// type FooVals = true | 0 | "a"
You could call this the ValueOf
operation, as mentioned in Is there a `valueof` similar to `keyof` in TypeScript? :
type ValueOf<T> = T[keyof T];
type FooVals2 = ValueOf<Foo>;
// type FooVals2 = true | 0 | "a"
In your case, though, you want to go one level deeper... for each key K
in keyof Names
, you want ValueOf
the property at that key. So that would be a nested ValueOf
:
type NamesICanUse = ValueOf<{ [K in keyof Names]: ValueOf<Names[K]> }>
We can unroll that manually to
type NamesICanUse = { [K in keyof Names]: Names[K][keyof Names[K]] }[keyof Names]
// type NamesICanUse = "Susan" | "Mike" | "Peter"
As desired.
There are ways to abstract this depending on use case. If you want a DeepValueOf<T>
that gives you the union of all non-object properties/subproperties/subsubproperties/etc of a type, you could write the following recursive conditional type:
type DeepValueOf<T> =
T extends object ? { [K in keyof T]: DeepValueOf<T[K]> }[keyof T] : T;
And this also works on Names
:
type NamesICanUse = DeepValueOf<Names>;
// type NamesICanUse = "Susan" | "Mike" | "Peter"
You could also hardcode the depth, or a max depth, etc., but I'll stop here.
CodePudding user response:
You can use the index signature, this will tell to Typescript to use as many properties you need with different names and their values will be Susan, Mike or Peter
type NamesICanUse = {
[nameKey: string]: 'Susan' | "Mike" | "Peter"
}
export class Names{
parents: NamesICanUse = {
mother: "Susan",
dad: "Mike"
}
children: NamesICanUse ={
son: "Peter"
}
}