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