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
]
}