Search code examples
javascripttypescripttypesmapped-types

How to create a Typescript type based on the keys/values of a dictionary?


Is there a way to automatically generate a type based on a dictionary value? E.g. given this dictionary of constant values:

export const SHIPMETHODS: OptionsMap<UpsShipMethodOption> = {
    'UPS': 'GND',
    'UPS AIR2': '2DA',
    'UPS 3DAY SELECT': '3DS'
}

Is there a way to automatically generate a type like this, without having to duplicate everything?

export type ShipMethodOptionMap = {
    'UPS': 'GND',
    'UPS AIR2': '2DA',
    'UPS 3DAY SELECT': '3DS'
}

Unfortunately keyof doesn't seem to work since it's an object and not a type, so this doesn't work:

export type UpsShipMethodOption = keyof SHIPMETHODS // Doesn't work

I also tried using mapped types with a predeclared union type of the keys, but it doesn't work either:

/* 
  Type 'KeyOptions' is not assignable to type 'string | number | symbol'.
  Type 'KeyOptions' is not assignable to type 'symbol'.ts(2322)
*/
export type OptionsMap<KeyOptions> = {
    readonly [Property in KeyOptions]: string;
}

export const SHIPMETHODS: OptionsMap<UpsShipMethodOption> = {
    'UPS': 'GND',
    'UPS AIR2': '2DA',
    'UPS 3DAY SELECT': '3DS'
}
export type UpsShipMethodOption = 'UPS' | 'UPS AIR2' | 'UPS 3DAY SELECT'

I have a bunch of such dictionaries, and most of them are quite large (30+ keys). Duplicating the dictionary into a type everywhere would make it a pain to maintain in case the values change, so any help with this would be great!


Solution

  • I think you just want the typeof operator. It simply gets the type of a value.

    const SHIPMETHODS = {
        'UPS': 'GND',
        'UPS AIR2': '2DA',
        'UPS 3DAY SELECT': '3DS'
    } as const // as const is important.
    
    type ShipMethodOptionMap = typeof SHIPMETHODS 
    /*
    type ShipMethodOptionMap = {
        readonly UPS: "GND";
        readonly 'UPS AIR2': "2DA";
        readonly 'UPS 3DAY SELECT': "3DS";
    }
    */
    
    type UpsShipMethodOption = keyof ShipMethodOptionMap
    // type UpsShipMethodOption = "UPS" | "UPS AIR2" | "UPS 3DAY SELECT"
    

    Just note the as const on your object which allows typescript to infer the string literals so they can become part of the type.

    Playground