TL;DR
Question: How can I create a type converter that gets the defined keys of objects typed with types with index signatures?
I want to create a type "converter" in TypeScript, which gets a type A and returns a new type B that have keys as all defined keys in A and accepts only string as values, like in the example:
type AToB<
T,
K extends keyof T = keyof T
> = { [id in K]: string };
const a = {
x : {}
}
const b : AToB<typeof a> = {
x : "here it works"
}
But when I use this in a object that already has a type with index signature defined, the keyof doesn't get the defined keys, e.g.:
type AWithSig = {
[id : string]: {};
}
const aSig : AWithSig = {
y: {},
z: {}
}
const bSig : AToB<typeof aSig> = {
y : "here the keys",
z : "are not shown :("
}
I tried it in the TSPlayground (link here) and it doesn't recognize the defined keys in aSig.
The problem isn't with AtoB
, but with the type of aSig
.
When you annotate a variable with a (non-union) type, that's the type of the variable. It doesn't gain any more specific information from the expression with which you initialize the variable.
So by the time you write const aSig: AWithSig = ⋯
you've already lost the type you care about.
Presumably the only reason you annotated in the first place was just to check that the type of the initializing expression was a valid AWithSig
. If so, you can instead use the satisfies
operator on this expression to perform the check, and then just allow the compiler to infer the type of aSig
afterward:
const aSig = {
y: {},
z: undefined // error!
// ~ <-- undefined is not assignable to {}
} satisfies AWithSig;
Oops, I made a mistake and the compiler caught it:
const aSig = {
y: {},
z: {}
} satisfies AWithSig // okay
And now the type of aSig
is
/* const aSig: {
y: {};
z: {};
} */
which will work as desired with AtoB
:
const bSig: AToB<typeof aSig> = {
y: "here it doesn't work :(",
z: "abc"
}
// const bSig: AToB<{ y: {}; z: {}; }, "y" | "z">