Take this string parsing function as an example.
const { a, b } = parseString({
string,
mapping: [{
param: 'a',
...
},{
param: 'b',
...
}]
Can i make typescript be aware that the only available keys should be 'a' and 'b'?
These are the types that I currently have.
interface ParseStringArgs {
string: string;
mapping: {
start: string | null;
end: string | null
param: string;
}[];
}
And my attempt at making it read the param key value.
Which obviously does not work now as it is (correctly) reading param: string;
from the ParseStringArgs
interface above.
export function parseString({ string, mapping }) {
...
type ParamKey = typeof mapping[number]['param'];
const result: Record<ParamKey, string> = {};
... some logic to populate result
return result
Now the result of the parseString
is Record<string, string>
Is it possible to make the return type of the function Record<'a' | 'b', string>
?
If you want a function's return type to depend in a general way on the types of its parameters, then you need to make the function generic. You need to do this explicitly by declaring type parameters on the call signature; they aren't inferred for you automatically (although there is a longstanding open feature request at microsoft/TypeScript#17428 for such functionality).
One way to do that looks like:
function parseString<const P extends ParseStringArgs>({ string, mapping }: P) {
type ParamKey = P["mapping"][number]['param'];
const result = {} as Record<ParamKey, string>;
return result
}
Here I've declared a type parameter P
constrained to ParseStringArgs
and made the function parameter of type P
. Note that it's a const
type parameter so that when callers use it, the compiler will tend to infer literal types for your inputs, which is what you want so that the "b"
value in your object literal is given the type "b"
and not string
(which would defeat the whole purpose).
Then the ParamKey
type is computed from P
via repeated indexed access. You don't want to use typeof
on mapping
since the compiler will helpfully widen that to something non-generic.
Oh and the result
initializer needs to be asserted as Record<ParamKey, string>
because {}
is almost certainly not of that type.
Okay, let's test it:
const { a, b, z } = parseString({
// -------> ~ error on z as expected
string: "xyz",
mapping: [{
param: 'a',
}, {
param: 'b',
}]
});
Looks good. The return type of the function is equivalent to {a: string, b: string}
and therefore trying to use destructuring assignment on z
is an error, as desired.