nickf
nickf

Reputation: 545975

How to override a property to be non-nullable in Typescript

The DefinitelyTyped definition of the Node built-in IncomingMessage (the type of req in the (req, res, next) arguments) has defined url to be nullable. Here's the snipped parts of the definition file:

// @types/node/index.d.ts
declare module "http" {
  export interface IncomingMessage {
    /**
     * Only valid for request obtained from http.Server.
     */
    url?: string;
  }
}

As the comment says, this is because this property is only valid when you're getting an instance of this IncomingMessage from the http.Server. In other uses it won't exist, hence, it's nullable.

However, in my case, I know that I'm only getting these instances from http.Server, and so it's kinda annoying that I can't just access the property without extra guards.

import { IncomingMessage, ServerResponse } from 'http';

function someMiddleware(req: IncomingMessage, res: ServerResponse, next: Function) {
  const myStr: string = req.url; // bzzzt.
  // Argument of type 'string | undefined' is not
  // assignable to parameter of type 'string'.
}

It's probably good to mention that I'm using TS 2.0.3 with strictNullChecks, which is not enabled on the Typescript Playground.

Here's the question. Is it possible to override that definition across my application so that url is not nullable?


Here's what I've already tried... adding this to one of my files:

declare module 'http' {
  interface IncomingMessage {
    url: string;
  }
}

...however that is disallowed: "Subsequent variable declarations must have the same type". This is explained in the documentation.

The only thing I can think of thus far is to create my own module which imports, extends and then exports the interfaces:

// /src/http.ts
import { IncomingMessage as OriginalIM } from 'http';
export interface IncomingMessage extends OriginalIM {
  url: string;
}

// src/myapp.ts
import { IncomingMessage } from './http'; // <-- local def

function someMiddleware(req: IncomingMessage) {
  const str: string = req.url; // all good
}

So, this works, but it seems so wrong.

Upvotes: 47

Views: 52290

Answers (7)

im.pankratov
im.pankratov

Reputation: 1878

In case somebody stumbles on this question when needing to remove null from possible property (or even multiple properties) values - ere's pretty simple type helper for that:

type NonNullableProperty<Record, Keys extends keyof Record> = {
    [K in keyof Record]: K extends Keys ? NonNullable<Record[K]> : Record[K];
};

Usage:

interface IncomingMessage { url: string | null; otherUrl: string | null; }

type IncomingMessageWithUrl = NonNullableProperty<IncomingMessage, 'url'>
type IncomingMessageWithUrlAndOtherUrl = NonNullableProperty<IncomingMessage, 'url' | 'otherUrl'>

It's a good alternative to provided answers because it preserves the structure of original interface, without breaking it into to interfaces like when using Pick or Omit: { url: string } & { ...otherProperties }.

Try it on TS playground

Upvotes: 0

Michał Turecki
Michał Turecki

Reputation: 3167

It is possible to override an argument of the middleware function to require url to be non-nullable, using intersection types:

function someMiddleware(req: IncomingMessage & { url: string }, res: ServerResponse, next: Function) {
  const myStr: string = req.url;
}

Upvotes: 0

zemil
zemil

Reputation: 5066

If you need to exclude null | undefined props from some object you can use:

type RequiredProps<T, P extends keyof T> = Omit<T, P> & Required<Pick<T, P>> & Record<P, NonNullable<T[P]>>;

For example:

interface ISome {
    p1: string;
    p2?: string | null;
    p3?: number;
}

const withRequiredProps: RequiredProps<ISome, 'p2' | 'p3'> = { p1: '1', p2: '2', p3: 3 } 

Upvotes: 0

ichigolas
ichigolas

Reputation: 7725

Here's a solution defining a utility type RequiredProperties:

type RequiredProperties<T, P extends keyof T> = Omit<T, P> & Required<Pick<T, P>>;

Example usage:

type Foo = {
    a?: any;
    b?: any;
    c?: any;
};

type Bar = RequiredProperties<Foo, 'a' | 'b'>;

const bar1: Bar = { a: 1, b: 2, c: 3 };
const bar2: Bar = { b: 2, c: 3 }; // fails because `a` is now required
const bar3: Bar = { c: 3 }; // fails because both `a` and `b` are missing

Upvotes: 10

CyberProdigy
CyberProdigy

Reputation: 999

In your sample case, it's easy because you want to get rid of ALL undefined, therefore use the Required utility type.

interface IncomingMessage { url?: string; }
type ValidMessage = Required<IncomingMessage>;

ValidMessage will have all properties required.

But for those coming here to find out how to get rid of ALL null, you can use this custom utility type.

export type NonNullableFields<T> = {
  [P in keyof T]: NonNullable<T[P]>;
};

interface IncomingMessage { url: string | null; }
type ValidMessage = NonNullableFields<IncomingMessage>;

ValidMessage will have all properties not null.

And for those coming here to find out how to get rid of null only for specific fields, you can use these custom utility types.

export type NonNullableFields<T> = {
  [P in keyof T]: NonNullable<T[P]>;
};

export type NonNullableField<T, K extends keyof T> = T &
NonNullableFields<Pick<T, K>>;

interface IncomingMessage { url: string | null; }
type ValidMessage = NonNullableField<IncomingMessage, 'url'>;

ValidMessage will have the property url not null.

Upvotes: 30

Casey Woolfolk
Casey Woolfolk

Reputation: 711

As of TypeScript 2.1, you can use a lookup type to access an interface property.

IncomingMessage['url'] // string | undefined

You can combine that with NonNullable to fit your use case.

NonNullable<IncomingMessage['url']> // string

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html

Upvotes: 41

nickf
nickf

Reputation: 545975

So I found a solution which is slightly less hacky.

TypeScript 2.0 also has added a non-null assertion operator: !

function someMiddleware(req: IncomingMessage) {
  const str1: string = req.url;  // error, can't assign string | undefined to string
  const str2: string = req.url!; // works
}

In my case, it's still a bit annoying, since there are many different files which need to access this property and so this non-null assertion is used in many places.

Upvotes: 28

Related Questions