What is the best way to build a versatile typeExclude helper function that can handle a wide range of types and is

I am currently working on developing a dynamic function that can assess whether a particular element meets certain criteria. This project serves as an extension to the original discussion found in this question.

One challenge I am facing is that the 'key' string is no longer accessible due to lack of a signature. How can I ensure that the 'key' remains dynamic for all incoming types?

type TypeA = { id: string; equipment: string; weight: string }
type TypeB = { id: string; equipment: string; material: string }
type TypeC = { id: string; equipment: string; width: string } 
type TypeD = { id: string; equipment: string; old: string } 
type TypeE = { id: string; equipment: string; recent: string }
type TypeF = { id: string; equipment: string; broken: string }

type AllTypes = (TypeA | TypeB | TypeC| TypeD | TypeE | TypeF )[]
const items : AllTypes = [{ id: '11245', equipment: 'hammer', recent: 'yes' }, { id: '11335', equipment: 'screwdriver', material: 'metal' }]


const typeExclude = (key: string, el: TypeA | TypeB | TypeC| TypeD | TypeE | TypeF) => {
       return key in el ? el[key as any /*the current type at hand*/] : undefined;
};

const arr = items.map(el => typeExclude('material', el) ? 'valid':'invalid')

console.log(arr)

Answer №1

It seems like the issue you're encountering is related to the limitation of in operator type narrowing working with string literals but not variables. This topic is further discussed in responses to this query.

The workaround involves creating your own custom type guard that validates the value of the key variable as a real key within the specified object.

type TypeA = { id: string; equipment: string; weight: string }
type TypeB = { id: string; equipment: string; material: string }
type TypeC = { id: string; equipment: string; width: string } 
type TypeD = { id: string; equipment: string; old: string } 
type TypeE = { id: string; equipment: string; recent: string }
type TypeF = { id: string; equipment: string; broken: string }

type Type = TypeA | TypeB | TypeC | TypeD | TypeE | TypeF;
type Key<U> = U extends U ? keyof U : never;

const items: Type[] = [
    { id: '11245', equipment: 'hammer', recent: 'yes' },
    { id: '11335', equipment: 'screwdriver', material: 'metal' }
];

const hasKey =
    <T extends object>(o: T, key: PropertyKey): key is keyof T => key in o;

const typeExclude =
    (key: Key<Type>, el: Type) => hasKey(el, key) ? el[key] : undefined

const arr = items.map(el => typeExclude('material', el) ? 'valid' : 'invalid');

console.log(arr);

Playground link

Furthermore, I've defined a distributive conditional type Key<U> that produces a union of keys from all types contained in the union U. This enforces the key parameter for your typeExclude() function to only accept recognized keys.

Answer №2

I have come across a solution to this issue, but I believe the following code is cleaner and more concise by using interfaces.

interface IType {
  id: string;
  equipment: string;
  weight?: string;
  material?: string;
  width?: string;
  old?: string;
  recent?: string;
  broken?: string;
}

const items: IType[] = [
  { id: "11245", equipment: "hammer", recent: "yes" },
  { id: "11335", equipment: "screwdriver", material: "metal" },
];

const typeExclude = <T extends keyof IType>(key: T, obj: IType) => {
  return key in obj ? obj[key] : undefined;
};

const arr = items.map((el) =>
  typeExclude("material", el) ? "valid" : "invalid"
);

console.log(arr); // ['invalid', 'valid']

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

Troubleshooting Inference Problems in TypeScript with Mapped Types and Discriminated Unions

There has been much discussion about typing discriminated unions, but I have not found a solution for my specific case yet. Let's consider the following code snippet: type A = {label: 'a', options: {x: number}; text: string}; // label serves ...

Issues with TypeScript: Difficulty locating names in HTML templates

I recently upgraded my Angular 7 code to Angular 9 by following the steps outlined in the Angular Upgrade guide. However, upon completion of the migration process, I started encountering numerous "Cannot find name" errors within the HTML templates of my co ...

Guide to establishing intricate conditions for TypeORM insertion

When attempting to insert data based on a specific condition, such as if shopId = "shopA", I want to include the shopdetail. In order to achieve this, I have implemented the following business logic, which is somewhat complex. Is there a more ef ...

Running a loop to utilize data from an array of objects within a Protractor it() spec

I am currently in the process of setting up an end-to-end test suite with specifications to assess the functionality of opening a file within the application. My goal is to collect performance data for each test specification, focusing on variables such as ...

Is there a way to retrieve the attributes of a generic object using an index in TypeScript?

I'm currently working on a function that loops through all the attributes of an object and converts ISO strings to Dates: function findAndConvertDates<T>(objectWithStringDates: T): T { for (let key in Object.keys(objectWithStringDates)) { ...

Use Angular's super.ngOnDestroy method for handling cleanup/unsubscribing

When it comes to unsubscribing / cleaning up from observables in Angular components (using ngOnDestroy), there are multiple options available. Which option should be considered the most preferable and why? Also, is it a good practice to include super.ngOnD ...

employing a variable within a function that is nested within another function

I'm encountering an issue where I am using a variable within a nested function. After assigning a value to it, I pass it to the parent function. However, when I call the function, nothing is displayed. function checkUserExists(identifier) { let user ...

The page does not appear to be updating after the onClick event when using the useState hook

Having difficulty re-rendering the page after updating state using the useState hook. Although the state value changes, the page does not refresh. export function changeLanguage(props: Props) { const [languageChange, setLanguageChange] = React.useState( ...

Enhancing Quill's capabilities to accommodate soft line breaks

My current challenge involves extending Quill with a custom Blot in order to allow newlines within <p> tags. Following the advice provided by the library author on a recent stackoverflow post, I have come up with the following code: import * as Quill ...

Deactivate the click functionality for an element located within a division using Angular and Typescript

Currently, I am working on a web development project using Angular/Typescript. I am searching for an efficient method to apply the (click) event only to a specific division and not to some selected elements within it. For instance, let's take this d ...

Please provide either a string or an object containing the proper key for TypeScript

Within my project, the languageSchema variable can either be a string or an object containing the 'Etc' key. The corresponding interface is defined as follows: let getLanguageSchema = (language: string): string => languagesSchemas[language]; ...

Encountered error: Unable to locate module - Path 'fs' not found in '/home/bassam/throwaway/chakra-ts/node_modules/dotenv/lib' within newly generated Chakra application

Started by creating the app using yarn create react-app chakra-ts --template @chakra-ui/typescript. Next, added dotenv with yarn add dotenv Inserted the following code block into App.tsx as per the instructions from dotenv documentation: import * as dote ...

Create an Interactive Form Generator in Angular

I'm currently working on an Angular project that involves Dynamic Inputs. When I click on a button, new inputs are created and now I want to use FormBuilder to get the data. However, I am unsure of how to go about this. Although I am familiar with us ...

Guidelines for saving a webpage as a PDF using TypeScript in Angular 8

Here is the code snippet for downloading the page directly: downloadPage(cmpName) { let downloadContents = document.getElementById(cmpName).innerHTML; let originalContents = document.body.innerHTML; document.body.innerHTML = downloadContents; // Add cod ...

Creating a popup trigger in ReactJS to activate when the browser tab is closed

I am currently working on an enrollment form that requires customer information. If a user fills out half of the form and then attempts to close the tab, I want to trigger a popup giving them the option to save and exit or simply exit. While I have a jQue ...

Error in Typescript compilation in Visual Studio Team Services build

I am currently working on an angular2 web application using Typescript 2.0. I have successfully installed version 2.0 locally in my Visual Studio and updated the tag for Typescript version in my project. The local build in VS works perfectly fine, but when ...

The TypeScript reflection system is unable to deduce the GraphQL type in this case. To resolve this issue, it is necessary to explicitly specify the type for the 'id' property of the 'Address'

import { ObjectType, ID, Int, Field } from 'type-graphql'; @ObjectType() export default class Address { @Field(type => ID) id: String; @Field() type: string; @Field() title: string; @Field() location: string; } More informa ...

Incorporating AppLozic's chat plugin into an Angular 5 project

Struggling to incorporate a third-party chat plugin into my Angular 5 project. The documentation is proving difficult to understand, and I can't figure out how to proceed. If anyone has successfully integrated it before, your help would be greatly app ...

What is the best way to merge an array of objects into a single object?

Is there a way to dynamically convert object1 into object2, considering that the keys like 'apple' and 'water' inside the objects are not static? const object1 = { apple:[ {a:''}, {b:'&apos ...

Is there a way to modify component data in Angular 5 from another component?

I have a dashboard setup where the main component stays consistent across all pages, while another component, the load component, changes based on the router-outlet. My goal is to hide the "manage load" button in the dashboard component if I delete all loa ...