Here's one possible implementation:
type ObjectPropertiesOptional<T> = (
Partial<T> & { [K in keyof T as T[K] extends object ? never : K]: T[K] }
) extends infer O ? { [K in keyof O]: O[K] } : never;
The whole thing is wrapped in (
...) extends infer O ? { [K in keyof O]: O[K] } : never
which is a trick to make the compiler convert a relatively ugly interesection type like {foo: string} & {bar: number}
and turn it into a single object type like {foo: string; bar: number}
. This is the same technique as Expand<T>
as described in How can I see the full expanded contract of a Typescript type?.
The actual implementation is to intersect Partial<T>
(using the Partial<T>
utility type to make every property optional) with a remapped version of T
that only includes non-object
properties. (By remapping a key to never
we suppress it).
So for {foo: string, bar: object}
, it would become {foo?: string, bar?: object} & {foo: string}
, which is equivalent to {foo: string, bar?: object}
(and converted to it by the extends infer O
trick).
Let's test it out:
interface MyObject {
name: string;
age: number;
favorites: {
color: string
}
}
type MyNewObject = ObjectPropertiesOptional<MyObject>;
/*
type MyNewObject = {
name: string;
age: number;
favorites?: {
color: string;
} | undefined;
}
*/
Looks good. The name
and age
properties are still required, but the favorites
property is now optional.
Playground link to code