DEV Community

Pragmatic Maciej
Pragmatic Maciej

Posted on

Advanced TypeScript Exercises - Question 5

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 {}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (6)

Collapse
 
kashyaprahul94 profile image
Rahul Kashyap

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>;

Enter fullscreen mode Exit fullscreen mode

Playground link

Collapse
 
alextsk profile image
alextsk • Edited

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>;
Enter fullscreen mode Exit fullscreen mode

--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>>>

Enter fullscreen mode Exit fullscreen mode
Collapse
 
kashyaprahul94 profile image
Rahul Kashyap • Edited

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>;
Enter fullscreen mode Exit fullscreen mode

Playground Link

Collapse
 
dwjohnston profile image
David Johnston

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,
}; 
Enter fullscreen mode Exit fullscreen mode

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.

Collapse
 
macsikora profile image
Pragmatic Maciej

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.

Collapse
 
drazbest profile image
dr-azbest • Edited
type UserByConfig<T extends Config> = {
  [K in keyof User as T[K] extends true ? K : never] -?: User[K]
} & {}
Enter fullscreen mode Exit fullscreen mode

Playground Link