Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reverse required and optional properties

Tags:

typescript

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.

like image 793
Remco Haszing Avatar asked Aug 21 '19 13:08

Remco Haszing


People also ask

What is optional property?

Optional Properties are those properties that are not mandatory. In the example below the person object name is usually mandatory but age may not be.

How do you mark a property as optional Typecript?

First, if you don't tell TypeScript that a property is optional, it will expect it to be set. Adding ? to the property name on a type, interface, or class definition will mark that property as optional. type Foo = { bar?: number; } const a: Foo = {}; // This is now OK!

What is optional property in TypeScript?

Optional property: In Typescript you can declare a property in your interface which will be optional. Suppose you have a interface for employee and middle name is optional then your code will look like this: interface IEmployee { firstName: string; lastName: string; middleName?: string; }

How do I make my interface properties optional?

The declaration of the interface is a place to make some properties optional. You can achieve that with a question mark next to the properties names. In the above example, the pet is an optional property.


2 Answers

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

like image 105
jcalz Avatar answered Oct 08 '22 19:10

jcalz


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;
like image 26
ChrisG Avatar answered Oct 08 '22 18:10

ChrisG