Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Non null property in map type #19608

Closed
aaronbeall opened this issue Oct 31, 2017 · 7 comments
Closed

Non null property in map type #19608

aaronbeall opened this issue Oct 31, 2017 · 7 comments
Labels
Duplicate An existing issue was already created

Comments

@aaronbeall
Copy link

TypeScript Version: 2.7.0-dev.201xxxxx

Feature request

It would be helpful when using strictNullChecks to have some way to map a type with optional fields to an equivalent type with non-nullable fields. I would expect using the ! non-null assertion like syntax somewhere in a map type:

  [P in K]: User[P]!;
  // or
  [P in K]!: User[P];

Why I want this

I've found that after turning on strictNullChecks I run into a lot of data types that have optional fields (in particular return values from http requests where all fields could be missing) that end up producing a lot of false flag "possibly null" errors in down-stream code. The problem is that even though I validate data at a high level, the data type holds onto the "possibly undefined" type wherever the data type is used in down-stream code, for example in my React component tree props. The truth is many of these components only get rendered when those fields are populated, but TS doesn't know this and probably couldn't possibly know this.

So the obvious solution is to create mirrors of the original data types that have certain fields defined as non-null... but this becomes quite cumbersome and leaky to maintain, especially since you rightfully can't assign something that has an optional field to something that doesn't.

In the end what I'm really trying to express is a type derived from a type with null | undefined removed from certain (or all) fields. Basically the opposite of Partial<>. And if I can do this generically I can eliminate a lot of the maintenance cost.

Code

// Example of a source type where anything could be undefined 
// (I encounter this a lot with swagger generated DTO types)
interface User {
  id?: string;
  name?: string;
}

// User with all fields made non-null
type CompleteUser {
  [P in keyof User]: User[P]!;
  // or
  [P in keyof User]!: User[P];
}

// Example
function isCompleteUser(user: User): user is CompleteUser {
  return user.id != null && user.name != null;
}

// Generic wrapper version
type Complete<T, K extends keyof T> = {
  [P in K]: T[P]!;
  // or
  [P in K]!: T[P];
}

// Example with generic validator type guard function
function isComplete<T, K extends keyof T>(obj: T, ...keys: K[]): obj is Complete<T, K> {
  return keys.every(key => obj[key] != null);
}

declare const user: User;
if (isComplete(user, "id", "name")) {
 // user = { id: string; name: string; }
}
@ghost
Copy link

ghost commented Oct 31, 2017

Maybe related to #14366

@mhegazy mhegazy added the Duplicate An existing issue was already created label Oct 31, 2017
@aaronbeall
Copy link
Author

Took a minute to understand #14366 but yes, I think that's basically the same thing.

@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@mhegazy
Copy link
Contributor

mhegazy commented Mar 2, 2018

This should be doable today using NonNullable type.

@aaronbeall
Copy link
Author

aaronbeall commented Apr 19, 2018

@mhegazy Would appreciate some insight into how this should work... I tried using NonNullable without success:

interface User {
  id?: string;
  name?: string;
}

type CompleteUser = {
  [P in keyof User]: NonNullable<User[P]>;
}

function isCompleteUser(user: User): user is CompleteUser {
  return user.id != null && user.name != null;
}

declare const user: User;

if (isCompleteUser(user)) {
  user.id // still string | undefined
  user.name // still string | undefined
}

How should this work?

@mhegazy
Copy link
Contributor

mhegazy commented Apr 20, 2018

Sorry for this use case, you can just use Required<User>.

the issue is NonNullable removes the undefined from the type, but since this is the a homomorphic transformation (i.e. the compiler knows that CompleteUser is a map on the types of the properties of User) it copies the optionality, which adds the undefined back again.

sorry for the wrong suggestion earlier.

@aaronbeall
Copy link
Author

@mhegazy Thanks! I somehow missed the new subtractive mapped type modifier syntax, that does exactly what I was asking here (and more)!

@microsoft microsoft locked and limited conversation to collaborators Jul 30, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

3 participants