Say I have a function like this:
static async getPets({ petType, inverseOrder }: { petType: string; inverseOrder?: boolean }) {
const [petsFound, totalFound] = await getPetsByType(petType, inverseOrder);
return {
[petType]: petsFound, // array topped to 20 results
totalFound // count all found without limit of 20
};
}
I want the return type of this function to be computed based on the petType
parameter, which can be any string, so that:
getPets({ petType: 'dogs' })
has return type{ dogs: any[], totalFound: number }
getPets({ petType: 'cats' })
has return type{ cats: any[], totalFound: number }
etc.
The current implementation has the return type { [x: string]: any; totalFound: any; }
which is not very useful since it doesn't check that the key returned is the same as the one used as param.
How do I do this?
CodePudding user response:
Not sure if this is optimized, but it works:
declare function getPetsByType (petType: string, inverseOrder?: boolean): Promise<[any[], number]>;
type Awaited<T> = T extends Promise<infer U> ? U : T;
type GetPetsResult<T extends string> = {
[k in T]: Awaited<ReturnType<typeof getPetsByType>>[0];
} & {
totalFound: Awaited<ReturnType<typeof getPetsByType>>[1];
};
async function getPets <T extends string>({ petType, inverseOrder }: {
petType: T;
inverseOrder?: boolean;
}): Promise<GetPetsResult<T>> {
const [petsFound, totalFound] = await getPetsByType(petType, inverseOrder);
return {
[petType]: petsFound,
totalFound,
} as GetPetsResult<T>;
}
async function main () {
const result = await getPets({petType: 'giraffe', inverseOrder: false});
console.log(result.giraffe.length === result.totalFound); // ok
console.log(result.cat.length === result.totalFound); // Error: Property 'cat' does not exist on type 'GetPetsResult<"giraffe">'.(2339)
}
CodePudding user response:
Not getting into implementation details of Promise
and focusing only on the return type based on the parameter passed, you could do the following:
// transform generic into literal type
type StringLiteral<T> = T extends string ? string extends T ? never : T : never;
// define function
function getPets<Key>({ petType }: { petType: StringLiteral<Key>}): Record<StringLiteral<Key>, number[]> & {totalFound: number;} {
return {
[petType] : [1, 2, 3],
totalFound: 123
} as Record<StringLiteral<Key>, number[]> & {totalFound: number}
}
// pass in 'petType' and get a a return type based on whatever you defined on that param
const {} = getPets({petType: 'cats'}) // return type : Record<"cats", number[]>