What is the best way to determine the data type of one property in an array of objects based on another property

I have been working on creating a straightforward parser that relies on rules provided by the constructor. Everything seems to be running smoothly, but there are some issues with data types.

interface RuleBase {
  parse(text: string, params: Record<string, any>): string;
}

class RuleA implements RuleBase {
  parse(text: string, params: { a: 'aa' }) {
    // parsing rules
    return '';
  }
}

class RuleB implements RuleBase {
  parse(text: string, params: { b: 'bb' }) {
    // parsing rules
    return '';
  }
}

class Parser<T extends Record<string, RuleBase>> {
  private parserRules: T;

  constructor(parserRules: T) {
    this.parserRules = parserRules;
  }

  parse(text: string, rules: { ruleName: keyof T; params: Parameters<T[keyof T]['parse']>[1] }[]) {
    let result = text;

    rules.forEach(({ ruleName, params }) => {
      const rule = this.parserRules[ruleName];
      if (rule) result = rule.parse(result, params);
    });

    return result;
  }
}

Here is an example of how it can be used:

const parser = new Parser({
  a: new RuleA(),
  b: new RuleB(),
});

// Example with correct parameters
parser.parse('Example1', [
  { ruleName: 'a', params: { a: 'aa' } },
  { ruleName: 'b', params: { b: 'bb' } },
]);
parser.parse('Example2', [
  { ruleName: 'b', params: { b: 'bb' } },
]);

// This will throw an error because 'b' only expects the parameter 'b'
parser.parse('Example3', [
  { ruleName: 'b', params: { a: 'aa', b: 'bb' } },
]);

// This will also throw an error as both rules have specific parameters they require
parser.parse('Example4', [
  { ruleName: 'a', params: { a: 'aa', b: 'bb' } },
  { ruleName: 'b', params: { a: 'aa', b: 'bb' } },
]);

In the case of Example3, the rule 'b' should only accept { b: 'bb' } for its parameters, but currently it allows other combinations unintentionally.

Is there a way to address this issue without altering the code's logic? Or should I consider revising the approach?

Answer №1

Initially, the behavior exhibited by the Typescript Compiler in this scenario is attributed to how it broadens types. The scanner observes and deduces that an array of a certain type is being created, and upon checking the type of elements within the array, it broadens the scope to include all possible values for it. While it's generally believed that this behavior is unavoidable, it typically entails formulating complex types tailored to a specific use case. Nevertheless, there is a way to address this issue -

interface RuleBase {
  parse(text: string, params: Record<string, any>): string;
}

type Keys<T> = keyof T;

type DiscriminatedUnionOfRecord<
  A,
  B = {
    [Key in keyof A as "_"]: {
      [K in Key]: [
        { [S in K]: A[K] extends A[Exclude<K, Keys<A>>] ? never : A[K] }
      ];
    };
  }["_"]
> = Keys<A> extends Keys<B>
  ? B[Keys<A>] extends Array<any>
  ? B[Keys<A>][number]
  : never
  : never;

// More code...

const parser = new Parser({
  a: new RuleA(),
  b: new RuleB(),
});

parser.parse('Example1', [
  { ruleName: 'a', params: { a: 'aa' } },
  { ruleName: 'b', params: { b: 'bb' } },
]);
parser.parse('Example2', [
  { ruleName: 'b', params: { b: 'bb' } },
]);

// Additional examples with error messages omitted for brevity
  

Ultimately, the goal is to prevent the Typescript scanner from excessively widening the type here. You can experiment further using this playground link.

IMPORTANT: As previously mentioned, this solution caters to the specific use case presented in the question. Any deviations in your actual use case may lead to unexpected outcomes.

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

Discovering the proper method for indicating the type of a variable in the middle of a statement

In my code, there was a line that looked like this: let initialPayload = this.db.list("/members").snapshotChanges() as Observable<any[]> But then I changed it to this: let initialPayload = this.db.list("/members").snapshotChanges ...

The function causes changes to an object parameter once it has been executed

I've encountered an issue with a function that is supposed to generate a string value from an object argument. When I call this function and then try to use the argument in another function, it seems to be getting changed somehow. Here is the code fo ...

A Typescript type that verifies whether a provided key, when used in an object, resolves to an array

I have a theoretical question regarding creating an input type that checks if a specific enum key, when passed as a key to an object, resolves to an array. Allow me to illustrate this with an example: enum FormKeys { x = "x", y = "y&q ...

Instead of displaying the name, HTML reveals the ID

I have defined a status enum with different values such as Draft, Publish, OnHold, and Completed. export enum status { Draft = 1, Publish = 2, OnHold = 3, Completed = 4 } In my TypeScript file, I set the courseStatus variable to have a de ...

Could adjusting the 'lib' compiler option to incorporate 'dom' potentially resolve TS error code TS2584?

My preferred development environment is Visual Studio where I write Typescript code. I am facing an issue where I simply want to use the command "document.write" without encountering an error. Unlike my previous PC and VS setup, I am now getting an error ...

The Environment variable in React Native is not functioning when utilizing TypeScript

After installing the react-native-dotenv library, I followed the instructions outlined in the TypeScript guide. I then created a .env file with the following contents: MARVEL_API = <url> MARVEL_PUBLIC_KEY = <public-key> MARVEL_PRIVATE_KEY = &l ...

Encountering a 404 error while attempting to test a contact form on a Next.js website using a local server

Trying to test a contact form in Next.js where the data is logged but not sent to the API due to an error. "POST http://localhost:3000/app/(pages)/api/contact/route.tsx 404 (Not Found)" Troubleshooting to identify the issue. [directory setup] ...

Ways to mock a static method within an abstract class in TypeScript

Having a difficult time testing the function Client.read.pk(string).sk(string). This class was created to simplify working with dynamoDB's sdk, but I'm struggling to properly unit test this method. Any help or guidance would be greatly appreciate ...

Having trouble retrieving JSON file in Next.js from Nest.js app on the local server

Having just started with Next.js and Nest.js, I'm struggling to identify the issue at hand. In my backend nest.js app, I have a JSON API running on http://localhost:3081/v1/transactions. When I attempt a GET request through postman, everything functi ...

Create dynamic breadcrumb trails using router paths

I am currently working on developing a streamlined breadcrumbs path for my application. My goal is to achieve this with the least amount of code possible. To accomplish this, I have implemented a method of listening to router events and extracting data fr ...

A step-by-step guide on how to simulate getMongoRepository in a NestJS service

Struggling with writing unit tests for my service in nestjs, specifically in the delete function where I use getMongoRepository to delete data. I attempted to write a mock but couldn't get it to work successfully. Here is my service: async delete( ...

Tips for preserving the status of radio buttons in a React application

I am currently utilizing a Map to keep track of the state of radio buttons, but I am facing challenges when it comes to correctly saving and updating it whenever a user makes a selection. The structure of my Map is as follows: { "Group A": [ ...

What is the best way to add a repository in Nest.js using dependency injection?

I am encountering an issue while working with NestJS and TypeORM. I am trying to call the get user API, but I keep receiving the following error message: TypeError: this.userRepository.findByIsMember is not a function. It seems like this error is occurring ...

Is it possible to overlook specific attributes when constructing an object using TypeScript interfaces?

I have defined an interface with various properties. I am looking to instantiate an object based on this interface, but I only want to partially initialize some of the properties. Is there a way to accomplish this? Thank you. export interface Campaign { ...

The configuration file tsconfig.json did not contain any input

After downloading angular2-highcharts through npm for my application, I encountered an error in the tsconfig.json file of the package while using Visual Studio Code: file: 'file:///c%3A/pdws-view-v2/node_modules/angular2-highcharts/tsconfig.json&apos ...

What is the best way to pass a URL as a prop in Next.js without encountering the issue of it being undefined

Within my file (handlers.ts), I have a function designed to post data to a dynamic API route while utilizing the app router. This function requires values and a URL for fetching. However, when I pass the URL as a prop like this: http://localhost:3000/unde ...

``There is an issue with Cross-Origin Resource Sharing (CORS) in a Node.js application utilizing TypeScript

I've encountered some issues with my application, specifically regarding CORS. I suspect it may be due to a misconfiguration on my server. The problem arises when I attempt to create a user in my PostgreeSQL database via the frontend. I have a tsx com ...

The Next.js template generated using "npx create-react-app ..." is unable to start on Netlify

My project consists solely of the "npx create-react-app ..." output. To recreate it, simply run "npx create-react-app [project name]" in your terminal, replacing [project name] with your desired project name. Attempting to deploy it on Netlify Sites like ...

Component in Next.js fetching data from an external API

I am attempting to generate cards dynamically with content fetched from an API. Unfortunately, I have been unsuccessful in finding a method that works during website rendering. My goal is to pass the "packages" as properties to the component within the div ...

Issue with React hook forms and shadcn/ui element's forwardRef functionality

Greetings! I am currently in the process of creating a form using react-hook-form along with the help of shadcn combobox. In this setup, there are two essential files that play crucial roles. category-form.tsx combobox.tsx (This file is utilized within ...