Custom type checker that validates whether all properties of a generic object are not null or undefined

In an attempt to create a user-defined type guard function for a specific use-case, I am faced with a challenge:

  • There are over 100 TypeScript functions, each requiring an options object.
  • These functions utilize only certain properties from the object while disregarding the others.
  • The type of the options object allows for null values, but if any necessary properties are null, the function should log the missing property name to the console and exit immediately.

My concept is to combine input validation, logging of invalid inputs, and a type guard into a single function that can accept an object. If any properties within the object are null or undefined, it will log the null property names to the console. The function should return true if all properties have non-null/non-undefined values, and false if any are null or undefined. Furthermore, upon returning, it should act as a type guard so that the object's properties can be referenced without having to cast them to non-nullable types.

Here's my initial approach:

type AllNonNullable<T> = { [P in keyof T]: NonNullable<T[P]> };
type StringKeyedObject = { [s: string]: any };

const allPropertiesHaveValuesLogged = <T extends StringKeyedObject>(values: T) : values is AllNonNullable<T> => {

  for (const key in Object.keys(values)) {
    if (values[key] == null) {
      console.log(`${key} is missing`);
      return false;
    }
  }

  return true;
}

I envisioned utilizing this function in a simple example like this:

interface Foo {
  prop1: string | null;
  prop2: number | null;
  prop3: {} | null;
}

const test1 = (foo: Foo): boolean => {

  if (!allPropertiesHaveValuesLogged(foo)) {
    return false;
  }
  const { prop1, prop2 } = foo;

  console.log(`${prop1.toLowerCase()} and then ${prop2.toFixed(0)}`);
  return true;
}

However, a major issue arises where all properties of foo are being checked instead of just the two properties used by the code. Some other properties may actually be allowed to be null, but the focus is specifically on prop1 and prop2.

In my subsequent attempt, I resorted to a verbose solution like this:

const test2 = (foo: Foo): boolean => {

  const propsToUse = { prop1: foo.prop1, prop2: foo.prop2 };

  if (!allPropertiesHaveValuesLogged(propsToUse)) {
    return false;
  }
  const {prop1, prop2} = propsToUse;

  console.log(`${prop1.toLowerCase()} and then ${prop2.toFixed(0)}`);
  return true;
}

Unfortunately, this method requires typing each property name multiple times and could lead to difficulties when renaming properties.

Finally, I came up with what I believe to be the most concise and least repetitive solution. However, TypeScript does not recognize that my type guard should apply to prop1 and

prop2</code. This might be due to the type guard being applied only to the anonymous object created during the function call.</p>

<pre><code>const test3 = (foo: Foo): boolean => {

  const {prop1, prop2} = foo;
  if (!allPropertiesHaveValuesLogged({prop1, prop2})) {
    return false;
  }

  console.log(`${prop1.toLowerCase()} and then ${prop2.toFixed(0)}`);
  return true;
}

Hence, #1 poses a runtime bug, #2 is cumbersome and prone to errors, and #3 results in compile errors which might get resolved in future TypeScript releases.

Is there a better solution that would work seamlessly on TypeScript 3.0? Perhaps even on 3.1?

Answer №1

One method is to send the attributes as strings to allPropertiesHaveValuesLogged. In Typescript, you can use keyof T for type-safe keys. This approach is more concise than version 2 and avoids creating additional objects.

interface Foo {
  prop1: string | null;
  prop2: number | null;
  prop3: {} | null;
}

type SomeNonNullable<T, TKey> = { [P in keyof T]: P extends TKey ? NonNullable<T[P]> : T[P] };
const allPropertiesHaveValuesLogged = <T, TKey extends keyof T>(values: T, ... props: TKey[]) 
                                        : values is SomeNonNullable<T, TKey> => {

  for (const key of props) {
    if (values[key] == null) {
      console.log (`${key} is missing`);
      return false;
    }
  }

  return true;
}

const test1 = (foo: Foo): boolean => {

  if (!allPropertiesHaveValuesLogged(foo, 'prop1', 'prop2')) {
    return false;
  }
  const { prop1, prop2 } = foo;

  console.log (`${prop1.toLowerCase()} and then ${prop2.toFixed(0)}`);
  return true;
}

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

Experiencing a problem with updating records in angular?

angular version: Angular CLI: 9.0.0-rc.7 I have encountered an issue while trying to update a record. After clicking on the edit icon, I am able to make changes to the record in the form. However, when I click on the Edit Button, the record gets updated i ...

Utilizing external applications within Angular applications

I have the task of creating a user interface for clocker, a CLI-based issue time tracker. Clocker functions as a stand-alone node.js application without any programming interface. To begin tracking time for an issue labeled 123, the command would typically ...

While utilizing Typescript, it is unable to identify changes made to a property by a

Here is a snippet of code I am working with: class A { constructor(private num: number = 1) { } private changeNum() { this.num = Math.random(); } private fn() { if(this.num == 1) { this.changeNum(); if(this.num == 0.5) { ...

Guide on effectively converting a table of tuples to an array of objects utility function (similar to zip) while preventing the merging of all values in typescript version 5.2.2

Almost there, but stuck on the final TS2322: Type TcolTuple[i] is not assignable to type string | number | symbol compiler error. Here's a nifty utility function called rowsToObjects() that many developers have probably come up with at some point, ...

Show a notification pop-up when a observable encounters an error in an IONIC 3 app connected to an ASP.NET

Currently, I am in the process of developing an IONIC 3 application that consumes Asp.NET web API services. For authentication purposes, I have implemented Token based auth. When a user enters valid credentials, they receive a token which is then stored in ...

How Can I Build a Dynamic Field Form Builder in Angular 4?

While working with dynamic JSON data, I needed to create fields dynamically. For instance, if my JSON array contains 3 values, I would generate 3 input checkboxes dynamically as shown below: <ng-template ngFor let-numberOfRow [ngForOf]="numberOfRows"&g ...

Creating an NPM package that utilizes global types without altering the project it is integrated with

The Dilemma: When working on a project that involves reusing multiple types across various files, utilizing types defined in a script file can be advantageous. These global types are accessible throughout the entire project without the need for importing, ...

The 'fullDocument' property is not present in the 'ChangeStreamDropDocument' type

Upon cloning an express TypeScript project, I encountered a Typescript error within a Mongo related function as mentioned in the title. Property 'fullDocument' does not exist on type 'ChangeStreamDocument<IUser>'. Property &apos ...

Convert the Angular PrimeNG class into a TreeNode object to avoid the error of trying to access the map property of an

Currently, I am working on a project that was created with JHipster and utilizes Angular 4.3. I want to incorporate the tree component from PrimeNG into this application. My aim is to transform an array of objects into an array of TreeNodes so that it can ...

What is the most effective method for delivering a Promise after an asynchronous request?

Currently, I am working on creating an asynchronous function in TypeScript that utilizes axios to make an HTTP request and then returns a Promise for the requested data. export async function loadSingleArweaveAbstraction(absId : string) : Promise<Abstra ...

Unable to encode value that is not an enumerated type

Working with my graphQL API using typescript and type-graphql, I am attempting to perform a mutation that has an inputType with an enum value defined as shown below export enum GenderType { female = 'female', male = 'male', } regis ...

Can the child component ensure that the Context value is not null?

In the process of developing a Next.js/React application with TypeScript, I've implemented a UserContext in pages/_app.js that supplies a user: User | null object to all child components. Additionally, there's a ProtectedRoute component designed ...

Error encountered: TypeScript module 'angularfire2/interfaces' not found in Ionic 3 with angularfire2-offline plugin

Encountering an error while trying to set up angularfire2-offline: [16:02:08] typescript: node_modules/angularfire2-offline/database/database.d.ts, line: 2 Cannot find module 'angularfire2/interfaces'. L1: import { Angula ...

Unable to set textAlign column property in Inovua React Data Grid using typescript

I am currently facing an issue with centering the content of each grid cell in Inovua RDG. A frustrating TypeScript error keeps popping up: Type '{ name: string; header: string; textAlign: string; defaultFlex: number; defaultVisible?: undefined; }&apo ...

activeStyle is not a valid property for type 'IntrinsicAttributes'

I encountered an issue while attempting to utilize NavLink in a react typescript project. The error message states: "Property 'activeStyle' does not exist on type 'IntrinsicAttributes & NavLinkProps & RefAttributes'." import Rea ...

There seems to be an issue with calling this particular expression. The elements within the type 'string | ((searchTerm: string) => Promise<void>) | []' are not all callable

Here is my unique useResults custom hook: import { useEffect, useState } from 'react'; import yelp from '../api/yelp'; export default () => { const [results, setResults] = useState([]); const [errorMessage, setErrorMessage] = us ...

Installation and execution of TypeScript jQuery / Bootstrap definition file on a local machine using npm typings: A step-by-step guide

Struggling to set up TypeScript jQuery and Bootstrap definition files in my new project using npm typings. Below are the steps I followed: 1- Open cmd, navigate to my project folder, and enter the following commands: npm install typings --global typings ...

Transferring Files from Bower to Library Directory in ASP.Net Core Web Application

Exploring ASP.Net Core + NPM for the first time, I have been trying out different online tutorials. However, most of them don't seem to work completely as expected, including the current one that I am working on. I'm facing an issue where Bower ...

Modifying the values of various data types within a function

Is there a more refined approach to enhancing updateWidget() in order to address the warning in the else scenario? type Widget = { name: string; quantity: number; properties: Record<string,any> } const widget: Widget = { name: " ...

"The ion-label in Ionic2 is cutting off some of the text and not displaying it

I'm currently facing an issue with ion-label inside ion-item. The description is not properly displaying and instead, it shows dot-dot.. ..I need the entire text to be visible. Is there any solution available? <ion-card *ngFor="let product of prod ...