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