2

I have something like { y: string; x?: number }, i need to get { y: string; x: undefined | number }. Is there a way to turn the first one into the second one via map types in typescript?

Trident D'Gao
  • 18,973
  • 19
  • 95
  • 159

2 Answers2

3

You can get the type you want using mapped types. You can get the optional and required keys from a type as described here

type OptionalKeys<T> = { [K in keyof T]-?:
  ({} extends { [P in K]: T[K] } ? K : never)
}[keyof T]

type RequiredKeys<T> = { [K in keyof T]-?:
  ({} extends { [P in K]: T[K] } ? never : K)
}[keyof T]

type Id<T> = { [P in keyof T]: T[P]}
type OptionalToUndefined<T> = Id<{
  [K in OptionalKeys<T>]-?: T[K] | undefined  
} & { 
  [K in RequiredKeys<T>]-?: T[K] 
}>
//  { y: number | undefined; x: boolean; }
type Foo = OptionalToUndefined<{ x: boolean; y?: number }> 
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • can you explain what this does: {} extends { [P in K]: T[K] } – Trident D'Gao Jan 16 '19 at 21:43
  • @AlekseyBykov I included a link to the original source of the types jcalz explains it there and you should read it and upvote him, but the idea is that : `{}` extends `{a?: string}`, but `{}` does not extend `{a: string}` or even `{a: string | undefined}` – Titian Cernicova-Dragomir Jan 16 '19 at 21:49
  • In other words, `{} extends { [P in K]: T[K] }` type condition is true if empty object is compatible with a type which has its properties in `K`, meaning that all these properties are optional. And the outer mapped type `{[K in keyof T] ...}` makes `K` go over properties from `keyof T` one by one, allowing the condition to select only optional ones (or required in the inverted case). – artem Jan 16 '19 at 21:49
  • you gave me an idea, here is my solution: `type EachNonOptional = { [K in keyof T]-?: T[K]; }` `type EachUndefinedOrNever = { [K in keyof T]: undefined extends T[K] ? undefined : never };` `type EachOptionalAsUndefined = { [K in keyof T]: EachNonOptional[K] | EachUndefinedOrNever[K] };` – Trident D'Gao Jan 16 '19 at 21:55
  • @AlekseyBykov not exactly the same. In your question the optionality is removed from the resulting type. The property can be `undefined` but it has to be present. The version in the comment keeps the property as optional. – Titian Cernicova-Dragomir Jan 16 '19 at 22:12
  • 1
    here i fixed it: `type EachNonOptional = { [K in keyof T]-?: T[K]; } type EachUndefinedOrNever = { [K in keyof T]: undefined extends T[K] ? undefined : never }; type EachOptionalAsUndefined = { [K in keyof EachNonOptional]: EachNonOptional[K] | EachUndefinedOrNever[K] };` – Trident D'Gao Jan 16 '19 at 22:14
  • jeez, it's even simplier than that: ```type EachNonOptional = { [K in keyof T]-?: T[K]; } type EachOptionalAsUndefined = { [K in keyof EachNonOptional]:T[K] }; ``` – Trident D'Gao Jan 16 '19 at 22:25
1

I ended up using this:

type EachNonOptional<T> = { [K in keyof T]-?: T[K]; }
type EachUndefinedOrNever<T> = { [K in keyof T]: undefined extends T[K] ? undefined : never };
type EachOptionalAsUndefined<T> = { [K in keyof EachNonOptional<T>]: EachNonOptional<T>[K] | EachUndefinedOrNever<T>[K] };
declare const x: EachOptionalAsUndefined<{ x?: number; y: string}>; // {x : number | undefined; x: string; }

or EVEN SIMPLIER:

type EachNonOptional<T> = { [K in keyof T]-?: T[K]; }
type EachOptionalAsUndefined<T> = { [K in keyof EachNonOptional<T>]: T[K] };
Trident D'Gao
  • 18,973
  • 19
  • 95
  • 159