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

Can you explain how the "reduce" function can be implemented using an interface in TypeScript?

Can you explain the "reduce" function using an interface in TypeScript? https://i.stack.imgur.com/X1VxL.png ...

Ways to encourage children to adopt a specific trait

Let's discuss a scenario where I have a React functional component like this: const Test = (props: { children: React.ReactElement<{ slot: "content" }> }) => { return <></> } When a child is passed without a sl ...

Incorporating Swift code into a NativeScript app

I'm attempting to integrate native Swift code into my NativeScript application. Despite following the guidelines provided in the documentation, specifically adding a Swift source file to App_Resources/iOS/src/ and using publicly exposed classes direct ...

GraphQL queries that are strongly-typed

Currently working on a Vue CLI project where I am utilizing axios as my request library. In all the examples I've come across, they use strings for queries like this: { hero { name friends { name } } } Given that I am employing ...

Creating an npm library using TypeScript model classes: A step-by-step guide

Currently, I am working on a large-scale web application that consists of multiple modules and repositories. Each module is being developed as an individual Angular project. These Angular projects have some shared UI components, services, and models which ...

How to customize the radio button style in Angular 11 by changing the text color

Hey guys, I'm working with these radio buttons and have a few questions: <form [formGroup]="myForm" class="location_filter"> <label style="font-weight: 500; color: #C0C0C0">Select a button: </label& ...

Property '{}' is not defined in type - Angular version 9.1.1

Currently, I am working with Angular CLI version 9.1.1 and I am attempting to update certain data without updating all of it. form: UserInfo = {adresse : {}}; UserInfo.interface export interface UserInfo { id_user: string; username: string; em ...

Issue with FullCalendar-vue and Typescript: the property 'getApi' is not recognized

Struggling to integrate FullCalendar-vue with Typescript, I encountered a problem when trying to access its API. This is how my calendar is set up: <FullCalendar ref="fullCalendar" :options="calendarOptions" style="width: 100%& ...

The requirement is for TypeScript to be cast as Partial<T>, with the condition that the keys

I'm currently looking for two different utility types: one that consists of a subset of a type with matching value types, and another that only requires the keys to exist in another type. I've devised the following solution, which appears to be ...

Removing an image from the files array in Angular 4: A step-by-step guide

I have recently started working with typescript and I am facing a challenge. I need to remove a specific image from the selected image files array before sending it to an API. app.component.html <div class="row"> <div class="col-sm-4" *ngFor ...

Tips on retrieving a strongly typed value from a method using Map<string, object>

Having had experience working with C# for a while, I recently ventured into a Node.js project using TypeScript V3.1.6. It was exciting to discover that TypeScript now supports generics, something I thought I would miss from my C# days. In my C# code, I ha ...

Stop modal from closing in the presence of an error

My approach involves using a generic method where, upon adding a food item, a modal window with a form opens for the user to input their details. However, since backend validation for duplicate items can only be retrieved after the API call completes. I w ...

Verifying TypeScript Class Instances with Node Module Type Checking

My current task involves converting our Vanilla JS node modules to TypeScript. I have rewritten them as classes, added new functionality, created a legacy wrapper, and set up the corresponding Webpack configuration. However, I am facing an issue with singl ...

Ways to conceal an element in Angular based on the truth of one of two conditions

Is there a way to hide an element in Angular if a specific condition is true? I attempted using *ngIf="productID == category.Lane || productID == category.Val", but it did not work as expected. <label>ProductID</label> <ng-select ...

Angular 2 decorators grant access to private class members

Take a look at this piece of code: export class Character { constructor(private id: number, private name: string) {} } @Component({ selector: 'my-app', template: '<h1>{{title}}</h1><h2>{{character.name}} detai ...

Guide on creating a similar encryption function in Node JS that is equivalent to the one written in Java

In Java, there is a function used for encryption. public static String encryptionFunction(String fieldValue, String pemFileLocation) { try { // Read key from file String strKeyPEM = ""; BufferedReader br = new Buffer ...

The type 'number | { percent: number; }' cannot be assigned to the type 'string | number | undefined' according to ts(2322) error message

Presently, I am engaged in a project utilizing React and Typescript, where I am working on implementing a progress bar using react semantic-ui. I have encountered a typescript error in my code. Here is the segment causing the issue: import React, { Compo ...

Modifying Data with MomentJS when Saving to Different Variable

After attempting to assign a moment to a new variable, I noticed that the value changes on its own without any modification from my end. Despite various attempts such as forcing the use of UTC and adjusting timezones, the value continues to change unexpec ...

Errors are not displayed or validated when a FormControl is disabled in Angular 4

My FormControl is connected to an input element. <input matInput [formControl]="nameControl"> This setup looks like the following during initialization: this.nameControl = new FormControl({value: initValue, disabled: true}, [Validators.required, U ...

Mastering the usage of Higher Order Components (HOC) with both types of props is

I am facing a challenge in implementing HOCs for this specific scenario. I aim to enclose existing components since they share similar functionalities. Below is an abridged version of my current structure: function CreateComponentHere(props: BaseProps): J ...