skip to Main Content

Let’s say I have an object that I want to make sure its keys conform to another type. I’m creating a dictionary of mapped keys, where on the left I have the original, and on the right, the displayed key. I need this available in both runtime and compile time, so doing this as const objects and arrays seems to make sense?

(Yes, I tried doing this with an array of tuples, but TypeScript is mad at me now).

const allowedKeys = ["cheese_type", "jalapeno_qty", "pepperoni"] as const;

const keyMap = {
   cheese_type:  "Cheese type",
   jalapeno_qty: "Number of jalapeños",
   pepperoni: "Pepperoni",
} as const;

This gives me a type that has all the keys mapped out explicitly:

const keyMap: {
    readonly cheese_type: "Cheese type";
    readonly jalapeno_qty: "Number of jalapeños";
    readonly pepperoni: "Pepperoni";
}

(I can export the types, too)

export const Keys = typeof allowedKeys
export const KeyMap = typeof keyMap

But I also want to protect the keyMap from accidentally misspelling or adding keys that it shouldn’t have, so I want to make it conform to the allowedKeys array.

But if I do that, I can’t figure out how to infer or define the object values as readonly/const properties…

const allowedKeys = ["cheese_type", "jalapeno_qty", "pepperoni"] as const;
type Keys = typeof allowedKeys;

const keyMap: {
  [key in Keys]: string // oh no
} = {
   cheese_type:  "Cheese type",
   jalapeno_qty: "Number of jalapeños",
   pepperoni: "Pepperoni",
} as const;

Is there any way I can do this that doesn’t make me run into crazy cycles like:

const keyMap: {
  [key in Keys]: key in keyMap ? keyMap[key] : never; // haha no
} = {
   cheese_type:  "Cheese type",
   jalapeno_qty: "Number of jalapeños",
   pepperoni: "Pepperoni",
} as const;

(Ultimately, I want to be able to have two sets of keys I can reference…)

export type RawKey = typeof keyMap;
export type DisplayKey = ValueOf<typeof keyMap>;

2

Answers


  1. You can do:

        const allowedKeys = ["cheese_type", "jalapeno_qty", "pepperoni"] as const;
        type Keys = typeof allowedKeys;
        const keyMap: Record<Keys[number], string> = {
            cheese_type:  "Cheese type",
            jalapeno_qty: "Number of jalapeños",
            pepperoni: "Pepperoni"
        } as const;
    

    This will prevent additional keys from being added to the keyMap.

    EDIT:
    In regards to pinks comment, you can do

    const keyMap: Readonly<Record<Keys[number], string>> = ...
    

    to make the keyMap readonly.

    Login or Signup to reply.
  2. You can use the satisfies operator to check that a value matches a particular type without widening it to that type which can throw away information you want.

    So as long as you have the type Keys of the keys you want to allow:

    const allowedKeys = ["cheese_type", "jalapeno_qty", "pepperoni"] as const;
    type Keys = typeof allowedKeys[number];
    

    You can say that keyMap satisfies Record<Keys, string>:

    const keyMap = {
        cheese_type: "Cheese type",
        jalapeno_qty: "Number of jalapeños",
        pepperoni: "Pepperoni",
    } as const satisfies Record<Keys, string>;
    

    and then keyMap still remembers all the literal types of its properties:

    /* const keyMap: {
        readonly cheese_type: "Cheese type";
        readonly jalapeno_qty: "Number of jalapeños";
        readonly pepperoni: "Pepperoni";
    } */
    

    And if you make mistakes with keyMap, the compiler should catch them:

    const extraKeyMap = {
        cheese_type: "Cheese type",
        jalapeno_qty: "Number of jalapeños",
        pepperoni: "Pepperoni",
        pineapple: "🍍⁉" // error!
    } as const satisfies Record<Keys, string>;
    
    const missingKeyMap = {
        cheese_type: "Cheese type",
        pepperoni: "Pepperoni",
    } as const satisfies Record<Keys, string>; // error!
    
    const misspelledKeyMap = {
        cheese_type: "Cheese type",
        jalapeno_qty: "Number of jalapeños",
        peppperroni: "Preppypony", // error!
    } as const satisfies Record<Keys, string>;
    

    Playground link to code

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search