Imagine I have multiple interfaces:
interface ClassA {
createMessage(basicInfo: string): string;
calculateStuff(a: number, b: number): number;
constant: number;
someAction(): void;
}
interface ClassB {
createTwoMessages(basicInfo: string): {message1: string, message2: string};
calculateDifferentThing(a: number, b: number): number
}
and so on.
How do I create a type which extracts return types of all the functions from the interfaces? It would be ideal if it would filter out stuff that is not a function or functions returning void. I need it to work like this:
type ExtractReturnTypes<T> = ???
type ReturnTypesOfClassA = ExtractReturnTypes<ClassA>
// = {createMessage: string, calculateStuff: number}
type ReturnTypesOfClassB = ExtractReturnTypes<ClassB>
// = {createTwoMessages: {message1: string, message2: string}, calculateDifferentThing: number}
Basically it is an extention of ReturnType generic type, but applied to an entire object instead of a single function, while also naming the return types using the original function names and filtering out things that do not return a value. But so far I was not able to create it. Does anyone have an idea how can I do this please?
You want ExtractReturnTypes<T> to be a mapped type over the properties of T, with some of the property keys suppressed if they don't correspond to functions with non-void return types. You can use key remapping with as to map the undesirable property keys to never:
type ExtractReturnTypes<T> = { [K in keyof T as (
T[K] extends (...args: any) => infer R ? (
[R, void] extends [void, R] ? never : K
) : never
)]: T[K] extends (...args: any) => infer R ? R : never
}
Note that in both the remapped key and the value type we have to compute the conditional type T[K] extends (...args: any) => infer R ? ⋯ : ⋯ to detect and extract the return type of the property function. This is redundant because you can't "cross" the boundary between the remapped keys and the mapped values of a mapped type. Anyway, that conditional type is similar to how the ReturnType<T> utility type is implemented.
For the key, we suppress the property key if the value is either not a function (the last never) or if it's a function that returns void. Detecting a void return is tricky because undefined is assignable to void (see Why is undefined assignable to void?) and presumably we don't want to suppress undefined (or at least that's not in the question). The check [R, void] extends [void, R] checks for mutual assignability between R and void. So if R is exactly void it will be caught. Otherwise it probably won't be caught (e.g., if R is narrower than void, wider than void, or unrelated to void).
Let's test it out:
interface ClassA {
createMessage(basicInfo: string): string;
calculateStuff(a: number, b: number): number;
constant: number;
someAction(): void;
}
type ReturnTypesOfClassA = ExtractReturnTypes<ClassA>
/* type ReturnTypesOfClassA = {
createMessage: string;
calculateStuff: number;
} */
interface ClassB {
createTwoMessages(basicInfo: string): { message1: string, message2: string };
calculateDifferentThing(a: number, b: number): number
}
type ReturnTypesOfClassB = ExtractReturnTypes<ClassB>
/* type ReturnTypesOfClassB = {
createTwoMessages: {
message1: string;
message2: string;
};
calculateDifferentThing: number;
} */
Looks good. The output types match what you wanted.
Playground link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With