cyrus-d
cyrus-d

Reputation: 843

Conditionally getting the type of interface property in typescript

Here is my interface:

interface Responses {
  200:{
    foo:number;
  };
  204:{
    bar:number;
  };
}

I can get any of the property type as follow:

type TypeFor200 = Responses['200'];

However I would like to make it conditional so that if 200 property is not exist in the interface, it should return the type for 204, something like this:

type AnyType= Responses extends 200 ? Responses['200'] : Responses['204'] ;

If both 200 and 204 exist, the above example will work, otherwise will throw an error complaining that the property does not exist.

Note that I cannot make a change to Responses interface since it is in an autogenerated file.

How should I go about doing this?

Upvotes: 1

Views: 1367

Answers (1)

Wing
Wing

Reputation: 9701

The conditional type you're going for seems to be a red flag for me. I can't think of a construct where this would be useful or necessary. TypeScript already errors when you try to access a property that does not exist on an interface.

interface Responses {
  200: {
    foo: number
  }
}

type NoContentResponse = Responses['204'];
                                   ^^^^^
              Property '204' does not exist on type 'Responses'.

TypeScript Playground

So you should just annotate the types correctly or break down your types so that you can use each fragment and compose them as necessary as opposed to use a conditional type like what you're trying to go for. However I can't think of everything so maybe you really have a use case for a conditional type.

The conditional you want is <key> extends keyof <interface>. This checks for the existence of a key. It would have to be constructed into a generic type. Something like this:

type Foo<A, B, C> = B extends keyof A ? A[B] :
                    C extends keyof A ? A[C] :
                    undefined;

An example with the resultant types is below:

interface OkResponse {
  200:{
    foo:number;
  };
}

interface NoContentResponse {
  204:{
    bar:number;
  };
}

interface NotAResponse {}

type OkAndNoContent = OkResponse & NoContentResponse;
type NoContent = NoContentResponse;

type Foo<A, B, C> = B extends keyof A ? A[B] :
                    C extends keyof A ? A[C] :
                    undefined;

type TypeForOk = Foo<OkAndNoContent, 200, 204>; // { foo: number; }
type TypeForNoContent = Foo<NoContent, 200, 204>; // { bar: number; }
type TypeForNotAResponse = Foo<NotAResponse, 200, 204> // undefined

TypeScript Playground

You can't do extends keyof outside of a generic type because TypeScript has enough type information to give you an error, another reason why this seems to be a red flag for me:

interface NoContentResponse {
  204:{
    bar:number;
  };
}

type TypeForNoContent = '200' extends keyof NoContentResponse ?
  NoContentResponse['200'] : NoContentResponse['204'];
                    ^^^^^
 Type '"200"' cannot be used as an index type.

TypeScript Playground

Upvotes: 2

Related Questions