Ensuring that an array in Typescript only contains non-null values

Within my object, there are certain values that may be null or undefined. If they are, I want to trigger an error and ensure Typescript acknowledges their non-null/undefined state when present. Initially, I can achieve this by checking each value individually:

if (!params.target || !params.value) {
  throw Error('Missing parameters')
}

const connection: { target: string; value: string } = {
  target,
  value
}

However, as the function has multiple values to validate, I prefer consolidating these checks into a single helper function. Utilizing the asserts keyword allows me to assert that an individual value is not null or undefined:

function checkMissingParams<T>(value: T): asserts value is NonNullable<T> {
  if (value === undefined || value === null) {
    throw Error(
      'Missing parameters'
    );
  }
}

Subsequently, in the main function, I implement:

checkMissingParams(params.target)
checkMissingParams(params.value)


const connection: { target: string; value: string } = {
  target,
  value
}

Nevertheless, my ultimate goal is to apply this technique across an array of values like so:

function checkMissingParams<T>(
  values: T[]
): asserts values is NonNullable<T[]> {
  const everyValueExists = values.every(
    value => value !== undefined && value !== null
  );
  if (!everyValueExists) {
    throw Error(
      'Missing parameters'
    );
  }
}

and consequently

checkMissingParams([params.target, params.value])

Yet, attempting this results in an error stating:

Type 'string | null | undefined' is not assignable to type 'string'.
  Type 'undefined' is not assignable to type 'string'.

when trying to execute:

const connection: { target: string; value: string } = {
  target,
  value
}

Can the asserts keyword be utilize over an array of values in this manner?

Answer №1

It is generally not feasible to achieve what you are requesting.

Functions that utilize type predicates (for example, x is T) or assertion predicates (such as asserts x is T) have the capability to narrow the perceived type of the specific single argument related to the parameter identified in the predicate only. If you define a function like

declare function f(x: any): asserts x is T
and have a variable foo, then invoking f(foo) will narrow the type of foo to T. (This also applies to properties, where f(foo.bar) will narrow the type of foo.bar to T.)

However, this narrowing does not extend to all other values whose type logically relies on the narrowed variable. For instance, if you have const baz = foo, then calling f(baz) will narrow baz but will not impact foo. Similar situations occur with literals like [foo] which cannot be accessed after use without being assigned to a variable. Therefore, methods like

checkMissingParams([params.target, params.value])
do not affect params.target or params.value.

Unfortunately, achieving the desired outcome may require restructuring your approach by creating custom functions or utilizing new variables as demonstrated in the provided code snippet.


To illustrate, consider adjusting the function to accept an object params along with a list of keys as arguments:

function checkMissingProps<T, K extends keyof T>(
    obj: T,
    ...props: K[]
): asserts obj is T & { [P in K]-?: NonNullable<T[P]> } {
    const everyValueExists = props.map(k => obj[k]).every(
        value => value !== undefined && value !== null
    );
    if (!everyValueExists) {
        throw Error('Missing parameters');
    }
}

This adjusted method narrows the object's type to

T & { [P in K]-?: NonNullable<T[P]> }
, ensuring non-nullability of the specified properties in K. This modification provides the intended functionality for narrowing types within an object.

Access the Playground link for testing the provided code

Answer №2

There is a way to handle this situation, though it unfortunately disrupts type assertion because TypeScript struggles to determine what occurs in the checkMissingParams function.

To address this issue, you can explicitly cast the types to inform the TypeScript compiler that you are certain the values will always be strings.

const connection = {
  target as string,
  value as string
}

Alternatively, you have the option to refactor the checkMissingParams function to ensure type-safe returns:

function checkMissingParams<T>(
  values: (T | undefined | null)[]
): asserts values is NonNullable<T[]> {
  const everyValueExists = values.every(
    value => value !== undefined && value !== null
  );

  if (!everyValueExists) {
    throw Error(
      'Missing parameters'
    );
  }

  return values as T[];
}

const [target, value] = checkMissingParams([params.target, params.value]);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Automate the process of opening an ngbpopover from an Angular 2 component using programming techniques

Currently, I am referring to this specific article in order to integrate Bootstrap with Angular 2. While the instructions in the article are helpful, there seems to be a lack of information on how to pass the popover reference to a component method. The on ...

What is the best way to employ document.addEventListener in TypeScript?

I am currently learning Ionic v2 and I am using document.addEventListener, but I am encountering errors as shown below: > [11:10:21] ionic-app-scripts 0.0.47 [11:10:21] build dev started ... [11:10:21] clean started ... [11:10:21] clean finished in ...

Executing npm and ng commands via an Ant script on a Windows machine leads to the error message "The specified file could not be found."

When attempting to execute the following Ant script, which runs the "npm" command: <target name ="test"> <exec executable="npm" failonerror="true"> <arg value="install" /> </exec> </target> An error occurs, i ...

Retrieve JSON data from a 404 response using the Http.get() method

I am attempting to retrieve JSON from a 404 response, but I am only receiving the Response {_body: "{myJSON}", status: 404, ok: false, statusText: "Not Found", headers: Headers…} How can I access the object itself so that I can display it in my HTML u ...

Transferring data between different components in Angular 5

Presently, I am working with two distinct components within my project - a navigation component and a detail component. The navigation component contains several options or items that the user can select. Upon selection by the user, I am attempting to upda ...

Setting default values for class members in Typescript is important for ensuring consistent behavior and

My model is pretty straightforward: export class Profile extends ServerData { name: string; email: string; age: number; } Whenever I make a server call using Angular 4's $http, I consistently receive this response: { name: string; email: ...

Express throwing module errors

I encountered an issue while attempting to expose a REST service in an electron app using expressJS. Following a tutorial, I added express and @types/express to the project. However, when trying to implement a "get" method and running the build with ng bui ...

Transforming React js into typescript by incorporating data into constants and interfaces

Recently, I've delved into working on React Typescript and embarked on creating a dropdown component using Semantic UI. While Semantic UI offers code in JavaScript format that is easier to comprehend, I encountered a roadblock when attempting to conve ...

What techniques can I use to modify an object while it's being iterated through?

I've been attempting to manipulate the object while looping through it, but unfortunately, it's not working. How can I modify it so that the constant patient includes the property lastActivity inside the this.model array? My code looks like this ...

Failure of Ngx-translate to propagate to subcomponents

Since I have implemented my translate module in the shared/header.module.ts file, it mainly serves the purpose of handling language switching for the entire application. In header.module.ts: @NgModule({ imports: [ TranslateModule.forRoot({ lo ...

Encountered a problem while attempting to post in Angular, receiving an error message stating "net::ERR

I recently started learning Nodejs. I've created an API on a local server using Mysql and I'm working on the frontend with Angular, while using Nodejs and Express as the backend. However, I'm facing an issue where my Angular app cannot conne ...

Is it possible to drive without nest.js?

I currently have a node-ts-express-setup that does not utilize nest.js. Unfortunately, the documentation and examples for drivine do not provide setup instructions without nest.js. Is there a way to use drivine without having to include nest as a dependen ...

What could be causing TypeScript to fetch the incorrect type definitions?

Every time I attempt to execute typescript in my project, I encounter the following issues: # ./node_modules/typescript/bin/tsc --project tsconfig.json node_modules/@types/webpack/index.d.ts:32:3 - error TS2305: Module '"../../tapable/tapable&qu ...

What is the best way to simulate the axios call within my express controller?

My axios call is already separated into a module and tested using mocked axios. However, I am facing a dilemma when it comes to testing the controller that imports this function. I understand that it needs to be mocked, but I am uncertain about the most ef ...

Exploring ways to customize or replace the extended CurrencyPipe type in Angular

I'm currently working on reusing an existing currency pipe from Angular common. My objective is to truncate the .00 when the value is round. Here's the code I've come up with: /** Transform currency string and round it. */ @Pipe({name: &apo ...

The type error in my nestjs application is caused by my attempt to access the property _id on the result of a promise, which does not exist on the type

Within my current project, I am encountering a type error when attempting to call _id on the user object. This issue arises because mongoose automatically defines the _id, meaning it is not present in my schema where the promise is defined. Upon changing ...

What is the best way to terminate a Node.js app using a scheduler?

I've been attempting to halt my cron task and shut down the entire nodeapp after 5 executions, but despite trying various methods, all attempts have failed. The message "time to quit" continues to appear in the log every minute. What would be the mos ...

When attempting to use a context, the type '...' cannot be assigned to type '...'

In my Next.js project, I am utilizing createContext to implement a dark mode button. The original jsx file for this functionality is called ThemeContext.tsx, and I am currently in the process of converting it to TypeScript. "use client"; import ...

Incorporating a CSS Module into a conditional statement

Consider the following HTML structure <div className={ `${style.cell} ${cell === Player.Black ? "black" : cell === Player.White ? "white" : ""}`} key={colIndex}/> Along with the associated CSS styles .cell { ...

Embracing the Power of React.StrictMode in Next.js

In my project using Next.js with React, I have React.StrictMode set to true in next.config.mjs. I'm wondering if it's still necessary to wrap my components with <React.StrictMode> in the layout.tsx file. Do both methods serve the same purpo ...