Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: How to use a generic parameter as object key

Is it possible to write an interface that accepts a string constant as one of its parameters, and uses that as the key of an object?

For instance, assuming I make two different GraphQL requests, both of which return a User, but under different key names:

const userByIdResult = {
  data: {
    userById: {
       id: 123,
       username: 'joseph'
    }
  }
}

const userByUsernameResult = {
  data: {
    userByUsername: {
       id: 123,
       username: 'joseph'
    }
  }
}

I would imagine writing a generic interface would go something like this:

interface GraphQLResponse<QueryKey, ResponseType> {
  data: {
    [QueryKey]: ResponseType
  }
}

interface User {
    username: string
    id: string
}

type UserByIdResponse = GraphQLResponse<'userById', User>
type UserByUsernameResponse = GraphQLResponse<'userByUsername', User>

But, this doesn't work.

like image 505
Good Idea Avatar asked Jun 02 '19 22:06

Good Idea


People also ask

How do you pass a generic type as parameter TypeScript?

Assigning Generic ParametersBy passing in the type with the <number> code, you are explicitly letting TypeScript know that you want the generic type parameter T of the identity function to be of type number . This will enforce the number type as the argument and the return value.

What does ?: Mean in TypeScript?

Using ?: with undefined as type definition While there are no errors with this interface definition, it is inferred the property value could undefined without explicitly defining the property type as undefined . In case the middleName property doesn't get a value, by default, its value will be undefined .

What TypeScript keyword is used to apply a constraint to a generic type parameter?

In TypeScript we can apply constraints on Generic type parameters (e.g. T) by using keyword extends (e.g. T extends Serializable).

Can you declare a variable with a generic type?

Yes it is possible, but only for Functions, not any arbitrary variable. As you can see, it's the type itself, where you define generics and then you can make a variable of that type, which allows it to set the generic.


1 Answers

You're close. This falls under the category of Mapped Types. You need to make two changes:

  1. QueryKey extends string
  2. key in QueryKey
interface GraphQLResponse<QueryKey extends string, ResponseType> {
    data: {
        [key in QueryKey]: ResponseType;
    }
}

interface User {
    username: string;
    id: number;
}

type UserByIdResponse = GraphQLResponse<'userById', User>;
type UserByUsernameResponse = GraphQLResponse<'userByUsername', User>;

Example Usage

const userByIdResult: UserByIdResponse = {
    data: {
        userById: {
            id: 123,
            username: 'joseph'
        }
    }
}

const userByUsernameResult: UserByUsernameResponse = {
    data: {
        userByUsername: {
            id: 123,
            username: 'joseph'
        }
    }
}

const userByIdResultBoom: UserByIdResponse = {
    data: {
        userByUsername: {
            id: 123,
            username: 'joseph'
        }
    }
}
like image 168
Shaun Luttin Avatar answered Sep 20 '22 15:09

Shaun Luttin