1

Given a type T how can I make all the properties that are object optional?

So for a type:

interface MyObject {
   name: string;
   age: number;
   favorites: {
     color: string
   }
}
type MyNewObject = ObjectPropertiesOptional<MyObject>;

it returns

interface MyNewObject {
   name: string;
   age: number;
   favorites?: {
     color: string
   }
}
Freddx L.
  • 455
  • 2
  • 7
  • 16
  • 3
    Something like [this approach](https://tsplay.dev/WP1eYw) might be what you want, but there are bound to be lots of edge cases (e.g., what do you want when the property is a union of an object type and a primitive? what about index signatures? arrays? etc) so it would be helpful if you'd test against your intended use cases and [edit] to include anything that goes wrong with the version shown here. Or, if it works for you as-is, let me know and I'll write it up as an answer. – jcalz Mar 07 '23 at 04:55
  • @jcalz for my current use case that works for me, I would like you post it as an answer – Freddx L. Mar 07 '23 at 21:04

2 Answers2

4

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

jcalz
  • 264,269
  • 27
  • 359
  • 360
0

As far as I am aware, this is not yet possible in Typescript.

Here's the Github discussion.

Maher
  • 151
  • 13
  • And yet [this playground link](https://tsplay.dev/WP1eYw) does it. It would be nice if there were some simple way to map properties to optional/required in a single mapped type, but you can get the same effect by combining multiple mapped types. – jcalz Mar 07 '23 at 13:49