Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Types from both keys and values of object in Typescript

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.

like image 493
dx_over_dt Avatar asked Dec 07 '18 01:12

dx_over_dt


People also ask

How do you find the type of the values of an object TypeScript?

Use keyof typeof to get a type that represents the object's keys. Index the object's type at the specific keys to get a type of its values.

How would you define a type of object key in TypeScript?

Use the keyof typeof syntax to create a type from an object's keys, e.g. type Keys = keyof typeof person . The keyof typeof syntax returns a type that represents all of the object's keys as strings.

How do you type a key value pair in TypeScript?

Use an index signature to define a key-value pair in TypeScript, e.g. const employee: { [key: string]: string | number } = {} . An index signature is used when we don't know all the names of a type's keys ahead of time, but we know the shape of their values. Copied!

What are the types of keys in objects?

Against what many think, JavaScript object keys cannot be Number, Boolean, Null, or Undefined type values. Object keys can only be strings, and even though a developer can use other data types to set an object key, JavaScript automatically converts keys to a string a value.


Video Answer


4 Answers

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"
like image 75
artem Avatar answered Oct 07 '22 21:10

artem


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

like image 39
setdvd Avatar answered Oct 07 '22 23:10

setdvd


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];
like image 18
AmerllicA Avatar answered Oct 07 '22 22:10

AmerllicA


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'
like image 2
lee_mcmullen Avatar answered Oct 07 '22 21:10

lee_mcmullen