Given this simplified example code:
type People = {
[key: string]: {
names: (string | number)[];
};
};
const people: People = {
bert: {
names: ["Bert", 1],
},
ernie: {
names: ["Ernie", 2, "The Ern"],
},
};
function getNames(person: People[string]) {
return person.names;
}
const names = getNames(people.bert); // names: string | number
In this case, I would like names
to be of type "Bert" | 1
.
So it should be inferred from the names property of whoever I pass into getNames
.
I am unable to figure out how I can use Typescript Generics to fix this. Can somebody help me with this? Many thanks.
#edit
After the answer of @ caleth, i am thinking of an example closer to the actual use-case, I am wondering if a simpler solution exists.
When i have this code:
const tests = {
test1: {
variants: ["variant1", "variant2"],
},
// ...
}
function getRandomVariant(test) {
// some random logic
return test.variants[0];
}
const variant = getRandomVariant(tests.test1);
// variant must be of type "variant1" | "variant2"
if(variant == "variant1") {
// ...
} else if(variant == "variant2") {
// ...
}
How to make this typesafe and use generics to get variant
to be of type "variant1" | "variant2"
?
First off you need to make sure that people
is inferred to a type where people.bert.names
still has that type, which means changing the definition of people
:
const people = {
bert: {
names: ["Bert", 1],
},
ernie: {
names: ["Ernie", 2, "The Ern"],
},
} as const satisfies People;
Then we can write a helper type to pull out the element type from names
:
type Names<T> = T extends { names: readonly (infer R)[]; } ? R : never;
Then we can put it all together with typing getFirstName
:
function getFirstName<T extends { names: any }>(person: T): Names<T> {
return person.names[0];
}