For example I have this type
type Foo = {
foo?: number
bar: string
obj?: {
qwe?: number
asd: string
}
}
and I want to have a type
type Foo2 = {
foo: number
obj: {
qwe: number
}
}
I have tried this
type OptionalKeys<T> = {[P in keyof T]: T[P] extends undefined ? P : never }[keyof T]
type PickOptionalProperties<T> = {
[P in OptionalKeys<T>]-?: PickOptionalProperties<T[P]>
};
type Foo2 = PickOptionalProperties<Foo>
const o: Foo2 = {
}
But it doesn't work and I'm not sure why
The first part of the problem is in your OptionalKeys
type. The relation is inversed, if you have a union, the union is the super type of a member, not the other way around . For example:
type N = number | undefined extends number ? "Y": "N" // will be "N"
type Y = number extends number | undefined ? "Y": "N" // will be "Y"
So in our case, OptionalKeys
will be:
type OptionalKeys<T> = {[P in keyof T]-: undefined extends T[P]? P : never }[keyof T]
We also need to exclude undefined
from the keys, since it will be in there because of the optionality of the properties.
The second part of the problem is how to construct a recursive type alias that will work correctly in all situations. For this we can turn to the DeepReadOnly
example found here
type Foo = {
foo?: number
fooArray?: number[]
bar: string
obj?: {
qwe?: number
asd: string
}
objArray?: Array<{
qwe?: number
asd: string
}>
}
type OptionalKeys<T> = Exclude<{ [P in keyof T]: undefined extends T[P] ? P : never }[keyof T], undefined>
type primitive = string | number | boolean | undefined | null
type PickOptionalProperties<T> =
T extends primitive ? T :
T extends Array<infer U> ? PickOptionalPropertiesArray<U> :
PickOptionalPropertiesObject<T>
interface PickOptionalPropertiesArray<T> extends ReadonlyArray<PickOptionalProperties<T>> { }
type PickOptionalPropertiesObject<T> = {
readonly [P in OptionalKeys<T>]: PickOptionalProperties<Exclude<T[P], undefined>>
}
type Foo24 = PickOptionalProperties<Foo>
const o: Foo24 = {
foo: 0,
fooArray: [1, 2],
obj: {
qwe: 1,
},
objArray: [
{ qwe: 1 }
]
}
Edit
As @jcalz pointed out a non strict null check version of this type is possible using:
type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T]
The way this works is by testing whether a pick of the P
property from he type, is assignable to {}
if so it means the property P
is optional as if it were required the resulting Pick
would not be assignable to {}
.
This version will actually do a better job of picking out just the optional properties, and required properties of type baseType | undefined
which my be a plus or a minus depending on your use case.