Typescript: Add a new type Required

Created on 5 Apr 2017  Â·  35Comments  Â·  Source: microsoft/TypeScript

It would be nice to have a new type that allows property to be required as opposited to Partial:

interface X {
    x?: string 
}

Required<X>{ }; // Property 'x is missing in type '{}'
Fixed Suggestion

Most helpful comment

I've found out that T & {} simplifies to non-nullable type, i.e.:

type Required<T> = {
  [P in Purify<keyof T>]: NonNullable<T[P]>;
};
type Purify<T extends string> = { [P in T]: T; }[T];
type NonNullable<T> = T & {};

Thanks to @falsandtru for the Purify method.

See: Playground (remember to enable strictNullChecks).

All 35 comments

This will probably depend on #13253 or #12215, whichever ends up getting implemented. You need a mapped type where you subtract undefined and null.

@masaeedu

type Required<T> = T & {
  [P in keyof T]: T[P];
}

let a: Required<{ x: number }> = { }; // Property 'x is missing in type '{}'

it seems to work well.

@monolithed That snippet doesn't have any optional properties in the type you're passing as a type argument to Required. This corrected snippet doesn't have the expected error:

type Required<T> = T & {
  [P in keyof T]: T[P];
}

let a: Required<{ x?: number }> = { };

@masaeedu, oh, you're right (

This is what you are looking for I believe. https://github.com/Microsoft/TypeScript/issues/13224

This is what you are looking for I believe. #13224

So, why the following operation [P in keyof T]: T[P] is not working as expected?

@monolithed
The operation is a homomorphic mapping, which have been configured to preserve optional and readonly modifiers. To get the desired behavior, you would need to do:

type RequiredInternal<T, K extends keyof T> = { [P in K]: T[P] }; // Use type parameter to force it not to be considered homomorphic
type Required<T> = T & RequiredInternal<T, keyof T>;

let a: Required<{ x?: number }> = { }; // => Property 'x' is missing in type '{}'

@PyroVortex No, I tried that yesterday (I think it might have been your comment on another issue). With tsc version 2.2.1, and the following files:

// tsconfig.json
{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "strictNullChecks": true,
        "noImplicitAny": false,
        "sourceMap": false
    }
}
// main.ts
type RequiredInternal<T, K extends keyof T> = { [P in K]: T[P] }; // Use type parameter to force it not to be considered homomorphic
type Required<T> = T & RequiredInternal<T, keyof T>;

let a: Required<{ x?: number }> = { }; // No error here
let x: number = a.x;                   // Error here because number | undefined is not assignable to number

The expected error does not appear, and a is typed as having a potentially undefined x property (number | undefined).

@masaeedu
Huh. In the current version in the TypeScript Playground with strictNullChecks enabled, I get an error for the first assignment (missing property), but I do still see the error you are mentioning for the second.

In general, we lack an ability to remove a type from a union using pure type manipulation (we can do it with an actual value).

@PyroVortex How did you enable strictNullChecks in playground?

image

@PyroVortex @masaeedu I am too trying to create type that maps optional fields to required fields. I get the example above to raise an error with TS 2.1.4 and in the playground (which it seems to be using). From 2.1.5 and up it does not give me an error anymore. Any idea why it is so?

This feature is very handy in react stateful components when you want to specify the default state, and you want to make sure you don't miss any state.

I'm still running into issues with this in TS 2.4.1. I've tried breaking the homomorphic relationship, as described in #13723, and I believe this does that because the fields are now required, but instead of removing all traces of optional parameters it keeps the type union with undefined. Is this intentional?

type RequiredInternal<T extends {[key: string]: any}, K extends string> = { [P in K]: T[P]};
type Required<T> = RequiredInternal<T, keyof T>;

// As expected: No compile error
const val1: Required<{x?: number}> = {x: 1};

// As expected: Compile error:
// TS2322: Type '{}' is not assignable to type 'RequiredInternal<{ x?: number | undefined; }, "x">'. Property 'x' is missing in type '{}'.
const val2: Required<{x?: number}> = {};

// Not expected?: No compile error
// Expected something like TS2322: Type '{ x: undefined; }' is not assignable to type 'RequiredInternal<{ x?: number | undefined; }, "x">'. Types of property 'x' are incompatible. Type 'undefined' is not assignable to type 'number'.
const val3: Required<{x?: number}> = {x: undefined};

@PyroVortex I'm afraid even with strictNullCheck, your example still results in a being optional (typescript 2.4):

image

Try this:

// Non-homomorphic keys
type NHKeys<T> = ({[P in keyof T]: P } & { [x:string]:never })[keyof T];

type Required<T> = {
  [K in NHKeys<T>]:T[K];
};

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

const foo:Foo = {b:'abc'};
const fooRequired:Required<Foo> = {b:'abc'}; // error

@sandersn @weswigham Is there a better way to do this?

I want to say... no? @ssonne seems to have it. The core is breaking the inference of a homomorphic relationship within the mapped type; and it seems like we keep that for as long as we can (traversing aliases and simplifying unions to look for it). The intersection-index indirection seems like a sound way to go.

@monolithed why don't you make PR --or better yet, invite @ssonne to do it because he figured it out-- to add Required<> to the types bundled by typescript itself

The PR is closed due to:

> I do not think this is a type we want to include in the standard library. if we were to address #15012, we need a type operator/ an addition to mapped types that allows such transformation.

@ssonne I think this is a bug, and I might have a fix in mind... so... I'm sorry?

@DanielRosenwasser So you’re saying this Required trick is about to be “fixed” out of existence? 😕

I use one of the variants of the aforementioned Required type definition in my projects, and it works for its purpose (requiring all fields to be set), but when I'm using this what I usually really want is to ensure that all fields are non-null and non-undefined. Required doesn't help us there since it still allows the field to be set as undefined. This was mentioned by @masaeedu here back in April.

Thankfully, there are a few tickets out for "subtraction" types which might solve this: #4183, #12215, #15059.

I just want to get clarification on this @DanielRosenwasser. Is the fact that this type works a quirk of a bug you think that @ssonne has found? So if we were to adopt this, it would stop working once the bug is fixed?

The workaround @ssonne has doesn't work in all contexts, unfortunately, too. For example, if you enable strictNullChecks:

interface Optional {
  foo?: () => {}
}
type Strict = Required<Optional>

const impl: Strict = {
  foo() {}
};

impl.foo();
// ^ Object is possibly 'undefined'.

[[Playground](https://www.typescriptlang.org/play/index.html#src=type%20NHKeys%3CT%3E%20%3D%20(%7B%5BP%20in%20keyof%20T%5D%3A%20P%20%7D%20%26%20%7B%20%5Bx%3Astring%5D%3Anever%20%7D)%5Bkeyof%20T%5D%3B%0D%0A%0D%0Atype%20Required%3CT%3E%20%3D%20%7B%0D%0A%20%20%5BK%20in%20NHKeys%3CT%3E%5D%3AT%5BK%5D%3B%0D%0A%7D%3B%0D%0A%0D%0Ainterface%20Optional%20%7B%0D%0A%20%20foo%3F%3A%20()%20%3D%3E%20void%0D%0A%7D%0D%0Atype%20Strict%20%3D%20Required%3COptional%3E%0D%0A%0D%0Aconst%20impl%3A%20Strict%20%3D%20%7B%0D%0A%20%20foo()%20%7B%7D%0D%0A%7D%3B%0D%0A%0D%0Aimpl.foo()%3B%0D%0A%2F%2F%20%5E%20Object%20is%20possibly%20'undefined'.%0D%0A)]

We can make required properties, but they still have nullable values. The last piece is NonNullable type.

type Required<T> = {
  [P in Purify<keyof T>]: NonNullable<T[P]>;
};
type Purify<T extends string> = { [P in T]: T; }[T];
type NonNullable<T> = T - undefined - null;

Well, right now we can have some workaround that allows you to define required type from partial one

type PartialPerson = {
  name? : number;
  surname? : string;
}

declare function makeRequired<T>(notRequired : Partial<T>): T;

let __requiredPersonVariable = null as any && makeRequired({} as PartialPerson);
type RequiredPerson = typeof __requiredPersonVariable;   

Then if we got #6606 we could do something like this (I _guess_, I'm not sure how it would work with generic type aliases if it was implemented):

type Required<T> = typeof( null as any && makeRequired({} as T))

For me It's another example how useful #6606 would be if we had it :)

I've found out that T & {} simplifies to non-nullable type, i.e.:

type Required<T> = {
  [P in Purify<keyof T>]: NonNullable<T[P]>;
};
type Purify<T extends string> = { [P in T]: T; }[T];
type NonNullable<T> = T & {};

Thanks to @falsandtru for the Purify method.

See: Playground (remember to enable strictNullChecks).

Great! @weswigham @sandersn @ahejlsberg Can we use type NonNullable<T> = T & {}; as a stable API?

Very nice @niieani! And it makes sense: {} contains all values except undefined and null, so intersecting with it excludes those and nothing else.

For those interested, I've added Required (and NonNullable) to Type Zoo.

Now the latest version is:

type Required<T> =
  T extends object
    ? { [P in Purify<keyof T>]: NonNullable<T[P]>; }
    : T;
type DeepRequired<T, U extends object | undefined = undefined> =
  T extends object
    ? { [P in Purify<keyof T>]: NonNullable<T[P]> extends NonNullable<U | Function | Class> ? NonNullable<T[P]> : DeepRequired<NonNullable<T[P]>, U>; }
    : T;

This type can stop the processing with the specified object types like DeepRequired<obj, Element>.

https://github.com/falsandtru/spica/blob/master/src/lib/type.ts
https://github.com/falsandtru/spica/blob/master/src/lib/type.test.ts

@falsandtru And as of today there's no need for Purify:

type Required<T> =
  T extends object
    ? { [P in keyof T]-?: NonNullable<T[P]>; }
    : T;
type DeepRequired<T, U extends object | undefined = undefined> =
  T extends object
    ? { [P in keyof T]-?: NonNullable<T[P]> extends NonNullable<U | Function | Class> ? NonNullable<T[P]> : DeepRequired<NonNullable<T[P]>, U>; }
    : T;

this should also handle assignability while things are still generic better, too; or at least that's the goal.

Right, I'll try to use the new syntax later.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

blendsdk picture blendsdk  Â·  3Comments

dlaberge picture dlaberge  Â·  3Comments

fwanicka picture fwanicka  Â·  3Comments

zhuravlikjb picture zhuravlikjb  Â·  3Comments

DanielRosenwasser picture DanielRosenwasser  Â·  3Comments