Types from both keys and values of object in Typescript

TypescriptTypescript Typings

Typescript Problem Overview


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 https://stackoverflow.com/questions/50822693/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.

Typescript Solutions


Solution 1 - Typescript

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"

Solution 2 - Typescript

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];

Solution 3 - Typescript

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.

IDE

Solution 4 - Typescript

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} />

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questiondx_over_dtView Question on Stackoverflow
Solution 1 - TypescriptartemView Answer on Stackoverflow
Solution 2 - TypescriptAmerllicAView Answer on Stackoverflow
Solution 3 - TypescriptsetdvdView Answer on Stackoverflow
Solution 4 - TypescriptNaeem BaghiView Answer on Stackoverflow