Remco Haszing
Remco Haszing

Reputation: 7809

Reverse required and optional properties

Let’s say I have the following type:

interface Foo {
  a?: string;
  b: string;
}

All properties can be made required using Required.

type Bar = Required<Foo>;

// Equivalent to
interface Bar {
  a: string;
  b: string;
}

All properties can be made optional using Partial.

type Bar = Partial<Foo>;

// Equivalent to
interface Bar {
  a?: string;
  b?: string;
}

I would like to make a generic type than flips the optional flag for all properties.

type Bar = Flip<Foo>;

// Equivalent to
interface Bar {
  a: string;
  b?: string;
}

The implementations for Partial and Required are pretty straightforward, but this Flip type would need to know which properties of Foo are optional and which are required. Is is possible to read this modified using a generic?

Some context: I believe this would be a useful type for defaultProps in React.

Upvotes: 11

Views: 1914

Answers (2)

DAG
DAG

Reputation: 6994

You can use a type system like this to construct Flip<T>:

type OptionalPropertyNames<T> = {
    [K in keyof T]-?: undefined extends T[K] ? K : never
}[keyof T];
type RequiredPropertyNames<T> = {
    [K in keyof T]-?: undefined extends T[K] ? never : K
}[keyof T];
type OptionalProperties<T> = Pick<T, OptionalPropertyNames<T>>
type RequiredProperties<T> = Pick<T, RequiredPropertyNames<T>>

type Flip<T> = Partial<RequiredProperties<T>> & Required<OptionalProperties<T>>;


type Bar = Flip<X>;

// ==
interface Bar {
  a: string;
  b?: string | undefined;
}

See this playground.

If you are ok with having Flip produce a type in which no property is optional, but instead the value as union with undefined you can it define like this:

type Flip<X> = {
    [K in keyof X]-?: undefined extends X[K] ? X[K] : X[K] | undefined;
};

Which produces the following type in your example:

type Bar = Flip<Foo>;

// Equivalent to
interface Bar {
  a: string;
  b: string | undefined;
}

Which should be sufficient in most cases and provides better autocompletion and "cleaner" types then the example above.

Explanation:

[K in keyof X]-?:

this makes every property required. The next bit (conditional types) checks if the type was required and creates a union with undefined in that case - making it effectively optional:

X[K] extends undefined ? X[K] : X[K] | undefined;

Upvotes: 3

jcalz
jcalz

Reputation: 328272

For your question as stated (where Foo has no index signature), you can define Flip (which I'll call FlipOptional) like this:

type OptionalKeys<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? K : never
}[keyof T];

type FlipOptional<T> = (Required<Pick<T, OptionalKeys<T>>> &
  Partial<Omit<T, OptionalKeys<T>>>) extends infer O
  ? { [K in keyof O]: O[K] }
  : never;

First we have to determine which keys of T are optional. There's no simple built-in way to do this, but the above OptionalKeys type alias above works by checking which single-property widenings of T are weak types, as described in answer to a related question. Index signatures mess this up, so if you need to operate on types with an index signature, you'll need something more complicated (which is also described in the linked answer).

Once you can figure out which keys are optional, it's relatively straightforward to construct an equivalent type to what you want, with Pick and Omit along with Partial and Required and an intersection: Required<Pick<T, OptionalKeys<T>>> & Partial<Omit<T, OptionalKeys<T>>>. The only problem is that this type is relatively ugly; it would be nicer if the type were written out as a single object type.

Here I use a trick with conditional type inference of the following form. Say we have a type Ugly which consists of a lot of intersections and mappings. To make it pretty, we can do

type Pretty = Ugly extends infer O ? {[K in keyof O]: O[K]} : never

Here, Ugly is essentially "copied" to the O parameter, which is mapped over without changing anything. This is mostly a no-op in terms of the output, but the result will be a single object type if possible.


Let's try it out:

interface Foo {
  a?: string;
  b: string;
}

type Bar = FlipOptional<Foo>;
/* type Bar = {
    a: string;
    b?: string | undefined;
} */

type FooAgain = FlipOptional<Bar>;
/* type FooAgain =  {
    b: string;
    a?: string | undefined;
} */

type MutuallyAssignable<T extends U, U extends V, V = T> = true;
type FooAgainIsFoo = MutuallyAssignable<Foo, FooAgain>; // okay

This all looks good. Bar is what you expect, and FlipOptional<Bar> gives a type equivalent to Foo.


Okay, hope that helps. Good luck!

Link to code

Upvotes: 12

Related Questions