Home > front end >  Typescript property name based on Generic Parameter Type
Typescript property name based on Generic Parameter Type

Time:11-28

Is there a way to create a Generic type in Typescript that contains a property based on the parameter type?

What I am looking for is something similar to mapped types. But instead of using the property names, I'd like to use the type name.

Example use case. I see a lot of APIs return the following structure

{
  "total": 100,
  "skip": 0,
  "limit": 30,
  "a property named based on the type being queried": [
    // the actual data requested
  ]
}

What I would like to do is create a generic that would be able to receive that data. I'm thinking something like

interface ListEnvelope<T> {
    [T as `${T}s`]: T[] // This is not valid Typescript. Its sudocode for what I'd like to do
    total: number
    skip: number
    limit: number
}
interface Product {
    id: number
    title: string
}
const ListEnvelope<Product> = {
  total: 100,
  skip: 0,
  limit: 30,
  Products: [
    // All of my products
  ]
}

That way I could program generically against all of the endpoints of the API.

CodePudding user response:

In TypeScript, type names are completely unobservable by the type system. (See Is it possible to get a string representation of a type name to use as a template literal type? and its answers for more information.) So there's no way in which you can automatically get the string literal type "Product" from the interface named Product. If you want behavior like this, you'll have to set it up yourself.

For example, you could add a second type argument to ListEnvelope which takes the desired key name:

type ListEnvelope<K extends string, T> = { [P in K]: T[] } & {
  total: number
  skip: number
  limit: number
}

which you would use like this:

type LEP = ListEnvelope<"Products", Product>;
/* type LEP = { Products: Product[]; } & 
   {  total: number; skip: number; limit: number; } */

const x: ListEnvelope<"Products", Product> = {
  total: 100,
  skip: 0,
  limit: 30,
  Products: [
    // All of my products
  ]
}

Or you could create a mapping from names to types in advance, like this:

interface TypeMap {
  Product: Product,
  OtherType: OtherType
  // ... all others
}

And then ListEnvelope would look up the name in that mapping:

type ListEnvelope<T extends TypeMap[keyof TypeMap]> =
  { [K in keyof TypeMap as T extends TypeMap[K] ? `${K}s` : never]: TypeMap[K][] } & {
    total: number
    skip: number
    limit: number
  }

which you would use like this:

type LEP = ListEnvelope<Product>;
/* type LEP = { Products: Product[]; } & 
   {  total: number; skip: number; limit: number; } */

const x: ListEnvelope<Product> = {
  total: 100,
  skip: 0,
  limit: 30,
  Products: [
    // All of my products
  ]
}

Playground link to code

  • Related