loading...

Advanced TypeScript Exercises - Answer 5

macsikora profile image Maciej Sikora Updated on ・2 min read

1. Conditional types solution

type Config = {
  name: boolean;
  lastname: boolean;
};
type User = {
  name?: string;
  lastname?: string;
};

declare function getUser<
  C extends Config,
  _NamePart = C['name'] extends true ? Pick<Required<User>, 'name'> : {},
  _LastNamePart = C['lastname'] extends true ? Pick<Required<User>, 'lastname'> : {}
  >(
    config: C
): _NamePart & _LastNamePart;

Check the solution in the playground.

Explanation

  • declaration have generic type C extends Config in order to be able to work with narrowed variable type
  • we have created two local variables _NamePart and _LastNamePart, the purpose is readability
  • _NamePart = C['name'] extends true ? Pick<Required<User>, 'name'> : {} - if type variable C has name property set at true we assign type which has required field name from the original type User
  • _LastNamePart = C['lastname'] extends true ? Pick<Required<User>, 'lastname'> : {} - in the same way as before we ask about lastname
  • _NamePart & _LastNamePart we return intersection type, it means depends on what we get in particular parts we join those parts.

Take a look that I have used monoid property as in both conditional types I just fallback to {} as neutral element of & operation.

2. Overloads solution

type Config = {
  name: boolean;
  lastname: boolean;
};
type User = {
  name?: string;
  lastname?: string;
};

declare function getUser(
  config: { name: true; lastname: false}
): Pick<Required<User>,'name'>;

declare function getUser(
  config: { name: false; lastname: true}
): Pick<Required<User>,'lastname'>;

declare function getUser(
  config: { name: false; lastname: false}
): {};

declare function getUser(
  config: { name: true; lastname: true}
): Required<User>;

Check the solution in the playground

The solution with overloads is less sophisticated but also longer, in order to achieve the result we need to create overload for every possible correlation of both fields in Config, so exactly 4 versions.

  • Pick<Required<User>,'name'> we pick only name field from Userand say its required.
  • Required<User> - we say we get User but with all fields non-optional

Utility types used in both solutions:

  • Pick - allows for picking only wanted set of properties
  • Required - produce a type with all properties required

Also want to mention more sophisticated solution by Rahul Kashyap left in the comment - Solution from the comments section. Good job Rahul!

This series is will continue. If you want to know about new exciting questions from advanced TypeScript please follow me on dev.to and twitter.

Discussion

pic
Editor guide