Home > Net >  Generic TypeScript API with types as parameters
Generic TypeScript API with types as parameters

Time:06-19

Let's say I have two functions like this (I currently have like 20 different that looks pretty much the same. Only difference is the route and the DTOs) I don't want to send both route and DTO in the function call as that would make the function calls more messy/complicated. So I'd rather only call it with either Asset | Personnel | Something else that is valid

 async getAllAssets() {
    return await this.get<AssetDto[]>('assets/assets')
  }

 async getAllPersonnels() {
    return await this.get<PersonnelDto[]>('personnel/personnels')
  }

And I want to make it more generic so I only need one function instead of two. How do I implement that? My own try is below. Maybe it will make it more clear what I actually want as well. I'm a total newbie with TypeScript and only been at it for one week. My "dream" implementation would also include enum so I could call the function with e.g. Entity.Asset or Entity.Personnel and then under the hood it then knows that it should use the the route and dto for either Asset or Personnel.

export type Asset = {
    route: '/assets/asset',
    dto: AssetDto[]
}

export type Personnel = {
    route: '/personnel/personnel',
    dto: PersonnelDto[]
}

export type Entity = Asset | Personnel

And here is the example of a more generic function:

 async getAll<T extends Entity>(entity: T) {
    return await this.get<typeof entity.Dto>(entity.route)
  }

But I don't know how to actually call the function with a type? Or is it even possible to do it like this?

  async howIWantAFunctionCallToBeLike() {
    await this.getAll(Entity.Asset))
  }

CodePudding user response:

As Tobias has said, you need something to survive to runtime to pass to your generic function. I recommend using a simple generic class:

class Route<T> {
  constructor(readonly path: string) {
  }

  transform(data: unknown): T[] {
    // unsafe cast by default; subclasses could do something more
    return data as T[];
  }
}

const personnelRoute = new Route<PersonnelDto>('/personnel/personnel');

async function getAll<T>(route: Route<T>): Promise<T[]> {
  return route.transform(await doFetch(route.path));
}

CodePudding user response:

It is possible to utilize generics here to infer the return type based on something passed into the function.

You could create an interface like this:

interface Entities {
  '/assets/asset': AssetDto[],
  '/personnel/personnel': PersonnelDto[]
} 

With this interface, we can create a generic function which returns the correct type based on the passed route.

async getGeneric<T extends keyof Entities>(route: T){
  return await this.get<Entities[T]>(route)
} 

async otherFn() {
  const a = await this.getGeneric('/assets/asset')
  //    ^? AssetDto[]
  const b = await this.getGeneric('/personnel/personnel')
  //    ^? PersonnelDto[]
}

Playground

  • Related