1

Is it possible to get the type of parameters of methods from a generic?

For example if I have:

interface Keys {
  create: any;
  ...
}

type MethodNames<T> = { [P in keyof Keys]: keyof T; }

Then is it possible to get the type of parameters of those Methods?

type MethodParams<T> = { [P in keyof Keys]: Parameters<T[???]>; }

Playground Link to Relevant Code

15
  • The first example isn't even getting the methods... so I'm unsure about what you need. Commented Oct 31, 2022 at 12:57
  • Sorry it's getting the method names. I'll update the above for clarity. Perhaps there's a much better way of what I'm trying to achieve. Commented Oct 31, 2022 at 13:00
  • How are you using the type MethodNames, though? Commented Oct 31, 2022 at 13:49
  • MethodNames is being used to call the named method of T. This is why I need the types of MethodParams. Effectively I'm mapping methods to CRUD. This is to create an abstraction where I can call CRUD without knowing the name of the method itself in the Abstract Class. Commented Oct 31, 2022 at 14:05
  • 1
    Is this approach what you're looking for? The only way to get the params is to know the actual mapping. If it meets your needs I can write up an answer explaining; if not, what am I missing? (Please mention @jcalz in your reply to notify me) Commented Oct 31, 2022 at 18:19

1 Answer 1

1

The only way I can imagine this working is if MethodParams is not only generic in T, the type of the underlying API, but also in M, the particular MethodNames<T> appropriate for T. And that could look like this:

type Keys = "create" | "getOne" | "getAll" | "update" | "delete"

export type MethodNames<T> = {
  [K in Keys]: keyof T;
};

export type MethodParams<T, M extends MethodNames<T>> = {
  [K in Keys]: T[M[K]] extends (...args: infer P) => any ? P : never;
};

(Note that I make Keys just a union of string literal types instead of making it an object type with values of type any that we ignore.)

So MethodParams<T, M> iterates over the keys in Keys and then looks up each key in M to get the key of T we want to probe for parameters. That is, if K is a member of Keys, we want to get the parameters from what we hope is a function type at T[M[K]]. We use conditional type inference to get that.


Let's test to see that it works. First I'll write a helper function to verify that a proposed method mapper works for a particular type T:

const methods = <T, M extends MethodNames<T>>(api: T, methods: M) => methods;

And now I'll make up a fake api:

interface SomeData {
  id: number,
  a: string,
  b: number,
  c: boolean
}
interface SomeApi {
  change(id: number, data: Partial<SomeData>): SomeData,
  destroy(id: number): boolean
  grab(id: number): SomeData | undefined,
  grabbingSpree(): SomeData[],
  make(data: Omit<SomeData, "id">): SomeData,
}
declare const someApi: SomeApi;

Here's the method mapper:

const someApiMethods = methods(someApi, {
  create: "make",
  getOne: "grab",
  getAll: "grabbingSpree",
  update: "change",
  delete: "destroy"
})

And now I can finally try MethodParams:

type SomeApiMethodParams = MethodParams<SomeApi, typeof someApiMethods>;
/* type SomeApiMethodParams = {
    create: [data: Omit<SomeData, "id">];
    getOne: [id: number];
    getAll: [];
    update: [id: number, data: Partial<SomeData>];
    delete: [id: number];
} */

Looks good. The type of SomeApiMethodParams is what we expect it to be.


Oh, and this also means that any class or type that needs to compute MethodParams will need to be generic in the appropriate MethodNames type. For example, your AbstractTestEnv class would be augmented with another type parameter:

export abstract class AbstractTestEnv<S, T, U, M extends MethodNames<S>> {
  public api: S;       
  public createDto: T;
  public crudMethods: M;    
  protected constructor(api: S, crudMethods: M) {
    this.api = api;
    this.crudMethods = crudMethods;
    this.createDto = this.generateCreateDto(this.resourceId);
  }   
  public abstract generateCreateDto(resourceId: string): T;
  public abstract getParams(): MethodParams<S, M>;
  /* snip */
}

Playground link to code

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.