I'm trying to declare a generic TypeScript function that accepts a Record<number, boolean>
and returns the same value.
I need the input type to be a generic type parameter, that is reflected in the output.
Unfortunately, calling the function, I don't get an error, when passing something other than a number (string
) as the record key:
const func = <T extends Record<number, boolean>>(input: T): T => input;
const x = func({
1: true,
test: false // <-- doesn't show error
})
It works if I inline the type Record<number, boolean>
, but then I'm not able to use it in my return type:
const func2 = (input: Record<number, boolean>): Record<number, boolean> => input;
const y = func2({
1: true,
test: false // <-- shows error
});
// ^? y: Record<number, boolean> - and not { 1: true }
How can I constrain my types, so I get errors for the input, but also preserve the output type?
Disclaimer: this is a simplified example. In the real code, more generic parameters exist and T
is used in multiple places, one of them being a more complicated return type based on T.
Object types in TypeScript are open/extendible and not sealed/closed/"exact". If you have a type constraint like T extends U
, it is quite possible for T
to have more keys in it than are mentioned in U
. There is a longstanding open issue at microsoft/TypeScript#12936 requesting "exact" types, so that presumably T extends Exact<U>
would prohibit T
from having any extra keys in it, but for now it's not part of the language.
So instead of T extends Record<number, boolean>
, which requires that all numeric keys have boolean
values, but which says nothing whatsoever about non-numeric keys, you could make a recursive constraint like this:
const func =
<const T extends { [K in keyof T]: K extends number ? boolean : never }>(
input: T
): T => input;
Essentially T
is constrained to a version of itself where all numeric keys have property values of type boolean
, while all other keys have property values of the impossible never
type. This will result in an error where you want it:
const y = func({ 1: true, test: false }); // error!
// ---------------------> ~~~~
// Type 'boolean' is not assignable to type 'never'.(2322)
Oh and since you apparently care about the literal type of the boolean
properties, you can make T
a const
type parameter as shown above, which (if you have no errors to mess things up), will preserve such information:
const x = func({ 1: true, 2: false });
/* const x: {
readonly 1: true;
readonly 2: false;
} */