Home > OS >  TypeScript Specify Value Type But Not Key Type of Map
TypeScript Specify Value Type But Not Key Type of Map

Time:10-06

I want to enforce a generic typing on a map such that every value of every key is a specific type (in this example A), but I do not want to override the underlying default key type. In the example below, if I specify the type of MY_MAP to be Record<string, A>, the MyMapKeys type changes from the desired key union 'unknown' | 'error' to string. How can I preserve the readonly keys of MY_MAP while also enforcing the map's generic value type to be A?:

type A = {
  name: string;
  description: string;
};

// MY_MAP must be a map of readonly string keys and type A values
const MY_MAP = {
  unknown: {
    name: 'unknown',
    description: 'unknown',
  },
  error: {
    name: 'error',
    // should display error: missing property "description"
  },
} as const;

// I want the following to be 'unknown' | 'error' not string
type MyMapKeys = keyof typeof MY_MAP;

CodePudding user response:

Typescript 4.9 (currently in beta as of Oct 5, 2022)

There is a new feature for this called the satisfies operator.

This allows you declare a value that is constrained to a type, and can be inferred to be a more specific type.

const MY_MAP = {
  unknown: {
    name: 'unknown',
    description: 'unknown',
  },
  error: {
    name: 'error',
  },
} as const satisfies Record<string, A>;
// Type '{ readonly unknown: { readonly name: "unknown"; readonly description: "unknown"; }; readonly error: { readonly name: "error"; }; }' does not satisfy the expected type 'Record<string, A>'.
//  Property 'error' is incompatible with index signature.
//    Property 'description' is missing in type '{ readonly name: "error"; }' but required in type 'A'.(1360)

See Playground


In Typescript 4.8 or lower

The work around for this has always been to use a generic identity function.

function makeMap<T extends Record<string, A>>(map: T): T {
  return map
}

// MY_MAP must be a map of readonly string keys and type A values
const MY_MAP = makeMap({
  unknown: {
    name: 'unknown',
    description: 'unknown',
  },
  error: {
    // Property 'description' is missing in type '{ name: string; }' but required in type 'A'.(2741)
    name: 'error',
  },
} as const);

See Playground

  • Related