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?
Asked
Active
Viewed 221 times
2

Trident D'Gao
- 18,973
- 19
- 95
- 159
2 Answers
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 – Trident D'Gao Jan 16 '19 at 21:55= { [K in keyof T]: undefined extends T[K] ? undefined : never };` `type EachOptionalAsUndefined = { [K in keyof T]: EachNonOptional [K] | EachUndefinedOrNever [K] };` -
@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
-
1here i fixed it: `type EachNonOptional
= { [K in keyof T]-?: T[K]; } type EachUndefinedOrNever – Trident D'Gao Jan 16 '19 at 22:14= { [K in keyof T]: undefined extends T[K] ? undefined : never }; type EachOptionalAsUndefined = { [K in keyof EachNonOptional ]: EachNonOptional [K] | EachUndefinedOrNever [K] };` -
jeez, it's even simplier than that: ```type EachNonOptional
= { [K in keyof T]-?: T[K]; } type EachOptionalAsUndefined – Trident D'Gao Jan 16 '19 at 22:25= { [K in keyof EachNonOptional ]:T[K] }; ```
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