Conditional property name in typesript interface depending on passed in type

Viewed 294

The title sound confusing but here's the gist of it.

I have a User interface that may or may not have some properties depending on from where it's fetched. Here's what I mean: (notice the optional properties role and client_details)

export interface User {
  id: string;
  first_name: string;
  last_name: string;
  email: string;

  // -- One of these in undefined depending on fetch location
  role?: Role;
  client_details?: ClientDetails;
}

The previous approach works, however having an undefined value every time doesn't look very pretty. It would be neeter if I could do this:

export interface User<ExtraType = Role | ClientDetails> {
  id: string;
  first_name: string;
  last_name: string;
  email: string;

  // -- Dynamic property name instead, much cleaner approach
  [ExtraType === Role ? 'role' : 'client_details']: ExtraType;
}

This doesn't work and gives a bunch of errors, mainly A computed property name cannot reference a type parameter from its containing type.

I'm aware I can simply make a static property extra & simply use it every time, but I wanted the property name to somewhat make sense. Is this feasible at all? Thank you!

1 Answers

What you need here is a disjoint union of types. In your case, it can be done like this:

interface UserBase {
  id: string;
  first_name: string;
  last_name: string;
  email: string;
}
interface UserWithRole extends UserBase {
  role: Role;
  client_details?: undefined;
}
interface UserWithDetails extends UserBase {
  client_details: ClientDetails;
  role?: undefined;
}

export type User = UserWithRole | UserWithDetails;

Now whereever you have a value of type User, you can check it like this:

function test(user: User) {
  if (user.role) {
    // here typescript knows that user is of type UserWithRole
  } else {
    // and here it knows that user is of type UserWithDetails
  }
}

I've added a declaration of missing property (property?: undefined) to both the types so typescript will not complain that User does not always have .role property; you can remove it and see the error in the if (user.role) line.

Here's the complete example

Related