loading...

Advanced TypeScript Exercises - Question 5

macsikora profile image Maciej Sikora ・2 min read

We have function getUser which gets Config object, the object defines what fields of User function will return. If for example config says { name: true, lastname: false } it means returned object should have name field non-optional but no field lastname. Current User type is very broad type of the return, we need to narrow it down depending on the config passed as argument of getUser. Solution should be done only at the type level, no value level code should be written. Only function declaration getUser is to be changed.

// Here types should remain the same ❄
type Config = {
  name: boolean;
  lastname: boolean;
};
type User = {
  name?: string;
  lastname?: string;
};

// Here declaration to be changed 🔥
declare function getUser(
     config: Config
): User;

// test cases
const user = getUser({ name: true, lastname: false })
user.name // this field should be non-optional
user.lastname // this field should not be there and we should have compile error 🛑

const user2 = getUser({ name: true, lastname: true })
user2.name // this field should be non-optional
user2.lastname // this field should be non-optional

const user3 = getUser({ name: false, lastname: true })
user3.name // this field should not be there and we should have compile error 🛑
user3.lastname // this field should be non-optional

const user4 = getUser({ name: false, lastname: false })
user4 // user4 should be empty object {}

Full code can be found in the playground.

Post your answers in comments. Have fun! Answer will be published soon!

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

Posted on by:

macsikora profile

Maciej Sikora

@macsikora

I am Software Developer, currently interested in static type languages (TypeScript, Elm, Reason) mostly in the frontend land. I am available for mentoring, I can help with type systems and FP.

Discussion

pic
Editor guide
 

Quick one -


type KeysOfTrueValues<T extends Config> = Pick<T, {
    [K in keyof T]: T[K] extends true ? K : never
}[keyof T]>;

type UserReturnType<T extends User, C extends Config> = Required<Omit<T, Exclude<keyof T, keyof KeysOfTrueValues<C>>>>;

// Here declaration to be changed 🔥
declare function getUser<T extends User, C extends Config>(
     config: C
): UserReturnType<T, C>;

Playground link

 

Slightly verbose one -

type KeysOfTrueValues<T extends Config> = Pick<T, {
    [K in keyof T]: T[K] extends true ? K : never
}[keyof T]>;

type OnlyTrueKeys<T extends User, C extends Config> = Exclude<keyof T, keyof KeysOfTrueValues<C>>;

type FilteredUser<T extends User, C extends Config> = Omit<T, OnlyTrueKeys<T, C>>;

type FilteredUserRequiredFields<T extends User, C extends Config> = Required<FilteredUser<T, C>>;

type UserReturnType<T extends User, C extends Config> = FilteredUserRequiredFields<T, C>;


// Here declaration to be changed 🔥
declare function getUser<T extends User, C extends Config>(
     config: C
): UserReturnType<T, C>;

Playground Link

 

tried to simplify your solution

type OptionalFields<T extends Config> = Pick<T,{
  [K in keyof T]: T[K] extends false ? K : never    
}[keyof T]>

type Result<C extends Config> = Required<Omit<User, keyof OptionalFields<C>>>

declare function getUser<C extends Config>(
     config: C
): Result<C>;

--edit
one more minor improvement

type OptionalFields<T extends Config> = {
  [K in keyof T]: T[K] extends false ? K : never    
}[keyof T]

type Result<C extends Config> = Required<Omit<User, OptionalFields<C>>>

 

Here's a simpler solution than has been posted:

// Here declaration to be changed 🔥
declare function getUser<T extends Config>(
  config: T
): {
  name: T["name"] extends true ? string : never,
  lastname: T["lastname"] extends true ? string : never,
}; 

Now in this case we're not getting compile errors - however the types of those values are never so depending on your use case - might be enough to prevent errors from occuring.

 

Hi David, again please pay attention I put 🛑 icon where the code should show compile error. Your implementation doesn't create such. So its not valid solution.