I have two sets of string values that I want to map from one to the other as a constant object. I want to generate two types from that mapping: one for keys and one for values.
const KeyToVal = {
MyKey1: 'myValue1',
MyKey2: 'myValue2',
};
The keys are easy enough:
type Keys = keyof typeof KeyToVal;
I'm having trouble getting a compile-time type for the values. I thought maybe one of these would work:
type Values = typeof KeyToVal[Keys];
type Values<K> = K extends Keys ? (typeof KeyToVal)[K] : never;
type Prefix<
K extends Keys = Keys,
U extends { [name: string]: K } = { [name: string]: K }
> = {[V in keyof U]: V}[K];
All of these just made Values
to be string
. I also tried adapting the two answers to How to infer typed mapValues using lookups in typescript?, but either I got my adaptations wrong, or the answers didn't fit my scenario in the first place.
The compiler will widen string literal type to string
, unless some specific conditions are met as explained in github issues and PR, or const assertion is used for literal value. Const assertions appeared in TypeScript 3.4:
const KeyToVal = {
MyKey1: 'myValue1',
MyKey2: 'myValue2',
} as const;
type Keys = keyof typeof KeyToVal;
type Values = typeof KeyToVal[Keys]; // "myValue1" | "myValue2"
Prior to 3.4, there was a workaround to get the same effect. To make the compiler infer literal types, you had to pass your object through a function with appropriately crafted generic type parameters, this one seems to do the trick for this case:
function t<V extends string, T extends {[key in string]: V}>(o: T): T {return o}
The whole purpose of this function is to capture and preserve types to enable type inference, it's entirely useless otherwise, but with it you can have
const KeyToVal = t({
MyKey1: 'myValue1',
MyKey2: 'myValue2',
});
type Keys = keyof typeof KeyToVal;
type Values = typeof KeyToVal[Keys]; // "myValue1" | "myValue2"
Actually, you should change the KeyToVal
to the below declaration:
const KeyToVal = {
MyKey1: 'myValue1',
MyKey2: 'myValue2',
} as const; // <----- add the <as const> here
Then create the keys types:
type Keys = keyof typeof KeyToVal;
Now you can create the types of the values:
type ValuesTypes = typeof KeyToVal[Keys];
You are trying to infer the type from the object (which can have any number of keys/values). You can try to describe the type (or maybe better an interface) first and then infer Kyes and Values like so:
type KeyToObjMap = {
some: "other",
more: "somemore",
};
type Keys = keyof KeyToObjMap;
type Values = KeyToObjMap[Keys];
let one: Values = "some";
let two: Values = "other";
let three: Keys = "some";
let four: Values = "somemore";
let five: Keys = "fun";
And you will have a correct highlight in IDE.
https://i.stack.imgur.com/fDwpG.png
KeyToObjMap
.
I know that it may not be related, but for my usecase, I reached this question because I wanted to create a type based on an object or array. So I just thought it may be useful for someone with the same usecase reaching this question to use enums: You can simply define an enum like this:
enum Arrow {
Up,
Down,
Left,
Right
}
You can read more about them here and here.
You can now use this enum as a type:
type Props = {
arrow: Arrow
}
const Component = (props: Props) => {
switch(props.arrow) {
case Arrow.Up:
// go-up
case Arrow.Down:
// go-down
...
}
}
and you can use it in your components:
<Component arrow={Arrow.top} />
Not quite the same, but if you have an array of objects instead of a single object then you can pull out the values of a known property to create a type by doing something like this:
const keyToValArray = [
{ value: 'myValue1', label: 'myLabel1' },
{ value: 'myValue2', label: 'myLabel2' }
] as const;
type Keys = typeof keyToValArray[number]['value']; // 'myValue1' | 'myValue2'
Success story sharing