Determine the Variable Type by Evaluating the Value of Another Variable

I am looking to implement the following functionality:

document("post").insert({ ... } /* TYPE SHOULD BE AUTOMATICALLY DETERMINED BY TYPESCRIPT */ ); 

The document() function returns an object that includes methods like insert. The returned object also has a document property, which is set to the value passed to the document() function. The object is of type DocumentObject, and methods like insert can access the document property using this.document. Everything is functioning correctly, but I want the insert method to accept an argument of a specific type that is determined by the value of this.document.

How can I define parameters with types that change based on the value of another variable? This is the signature of the insert method:

export default async function (this: DocumentObject, record: ???): Promise<...> {...}

The variable this.document (accessible within the function) can have values like post, user, or comment. I have corresponding types for each possible value: Post, User, Comment. This is how this.document is defined:

document: "post" | "comment" | "user" = ...;

My Query Is: How can I use Typescript to map each value to its relevant type, so I can assign that type to the record parameter? Is this achievable in Typescript?

Please Note: This is not a duplicate of Conditional parameter type based on another parameters' value or Conditional type based on the value of another key; They suggested using function overloads. Are there alternative methods to achieve this?

Answer №1

To start, it is advisable to create a mapping interface that defines the connection between the document property and the acceptable type for the insert argument:

interface DocumentMap {
    post: Post;
    user: User;
    comment: Comment;
}

Subsequently, we can generalize the DocumentObject to be generic using the type K for the document property as follows:

interface DocumentObject<K extends keyof DocumentMap> {
    document: K;
    insert(record: DocumentMap[K]): Promise<any>;
}

Here, K is restricted to keyof DocumentMap, which translates to the union type

"post" | "user" | "comment"
as required. Additionally, the insert() method now accepts a record parameter of the indexed access type DocumentMap[K].

Lastly, your document function is also generic, taking the type K for its argument and yielding a value of type DocumentObject<K>:

declare function document<K extends keyof DocumentMap>(k: K): DocumentObject<K>;

Let's put it to the test:

document("post").insert(post); // valid
document("user").insert(user); // valid
document("comment").insert(comment); // valid
document("user").insert(post); // error

Seems to be working fine. When you call document("user"), you receive a

DocumentObject<"user">
, which equates to
{document: "user", insert(record: User): Promise<any>}
, indicating that the insert() method takes a User input and rejects a Post input.

Link to code snippet for testing

Answer №2

If you want to establish a connection between the document key and the specific types, you can define a new type that does this mapping. Utilize generics in your function to link the indexed types.

type PostDocument = {author: string}
type UserDocument = {name: string}
type CommentDocument = {text: string}

type DocumentObject = {
  document: "post" | "user" | "comment"
}

type DocumentTypes = {
  post: PostDocument,
  user: UserDocument,
  comment: CommentDocument
} 

async function insertDocument<D extends DocumentObject, R extends DocumentTypes[D["document"]]> (doc: D, record: R) {
    ...
}

This way, the type of the second parameter is determined by the value of the document field.

insertDocument({document: "post"}, {author: "a"})
insertDocument({document: "user"}, {name: "b"})
insertDocument({document: "comment"}, {text: "c"})
insertDocument({document: "comment"}, {name: "d"}) // TypeError
insertDocument({document: "user"}, {author: "e"}) // TypeError

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

Why is my custom Vuelidate validator not receiving the value from the component where it is being called?

On my registration page, I implemented a custom validator to ensure that the password meets specific criteria such as being at least 12 characters long and containing at least one digit. However, I encountered an issue where the custom validator was not r ...

What methods can I use to locate the circular dependency within my program?

I am facing numerous circular dependency errors in my Angular project, causing it to malfunction. Is there a way to identify the section of the code where these circular dependencies exist? Warning: Circular dependency detected: src\app&bs ...

Enhancing many-to-many relationships with additional fields in Objection.js

I have a question that I haven't been able to find a clear answer to in the objection.js documentation. In my scenario, I have two Models: export class Language extends BaseId { name: string; static tableName = 'Languages'; st ...

Is it possible to use a Jasmine spy on a fresh instance?

In need of assistance with testing a TypeScript method (eventually testing the actual JavaScript) that I'm having trouble with. The method is quite straightforward: private static myMethod(foo: IFoo): void { let anInterestingThing = new Interesti ...

AngularJS and TypeScript encountered an error when trying to create a module because of a service issue

I offer a service: module app { export interface IOtherService { doAnotherThing(): string; } export class OtherService implements IOtherService { doAnotherThing() { return "hello."; }; } angular.mo ...

Utilizing Angular Ionic to Extract and Showcase Information Derived from Other Data

I am attempting to show a date that corresponds to a specific order status. However, the current output is incorrect as it displays all dates for each order instead of just the relevant one. https://i.sstatic.net/FRy0z.png Below is my .ts code: construc ...

Experimenting with error boundaries by utilizing the React Testing Library and Jest:

I am facing an issue while writing a test for my error boundary higher-order component (HOC). The problem arises when I mock throwing an error in my wrapped component, causing the test to fail because it recognizes the error as a genuine one instead of und ...

Specialized pipe encountering issues with conditional statements that function properly within a function

When using a custom pipe that returns a string based on certain conditions, it seems to always return the first result even when the conditions are not met. However, when the same if/else statements are used in a method, it works fine. transform(orderTy ...

The local storage gets wiped clean whenever I am using this.router.navigate

I am in the process of building a website using Angular 5 and Typescript. One important aspect of my implementation is utilizing localStorage to store the JWT Token for user login. Whenever I click on a link (either Home or any other link), I implement a ...

What is the recommended method for deleting sequelize.connectionManager.getConnection according to the Sequelize documentation?

I am currently developing an AWS Lambda function using Typescript that interacts with a database through Sequelize. According to the official Sequelize documentation, the configuration for Sequelize should be as follows: let sequelize = null; async func ...

Error: The lockfile and package.json file are not synchronized when running npm

Having a problem with NPM where the package-lock and package.json files are out of sync. Tried deleting node_modules, running npm install, but issue persists. Any suggestions? Error: npm ci can only install packages when package.json and package-lock.json ...

Using Typescript for-loop to extract information from a JSON array

I'm currently developing a project in Angular 8 that involves utilizing an API with a JSON Array. Here is a snippet of the data: "success":true, "data":{ "summary":{ "total":606, "confirmedCasesIndian":563, "con ...

The JSX element 'HeaderPublic' does not contain any construction or calling signatures

I am currently utilizing nx workspace to build the react ts application. Below is the library component: import { ReactElement } from 'react'; import styles from './header-public.module.scss'; export function HeaderPublic(): ReactElem ...

Display responsive input field based on selected option in ionic2 dropdown menu

When the user selects 'Other' from the dropdown menu using Ionic2 and Angular2, I want to provide them with an option to enter their profession. Here is a visual representation of the select box: https://i.sstatic.net/CRjAl.png Below is the co ...

Ways of modifying the readonly and required attributes of an HTML element using Angular2 Typescript

I am facing an issue with changing input field attributes back and forth in some of my components. I have a code that successfully changes the readonly attribute as needed. However, when trying to change the required attribute, Angular2 still considers the ...

Ways to broaden React categories for embracing html attributes as props?

When designing a component that accepts both custom props and HTML attribute props, what is the best approach for creating the interface? The ideal interface should also accommodate React-specific HTML props, such as using className instead of class. Here ...

Exploring LocalStorage Monitoring in Vue.js 2

How can I stay informed about any changes in Banana.vue? I have tried using addEventListener, @watch but it doesn't seem to be working... The index.vue file is importing both Apple.vue and Banana.vue In Apple.vue: localStorage.setItem('fruit ...

Is it possible to manipulate an Angular #variableName in order to retrieve an ElementRef for an HTML element?

Suppose I have a scenario where I create a button like this: <button #myButton>My Button</button> ...and then use ViewChild in the following way: @ViewChild('myButton', { static: true }) createButton: ElementRef; In this case, creat ...

Tips for using conditional rendering with React and TypeScript

Issue with Conditional Rendering in TypeScript It seems like I might have encountered a problem with the way I declare my components. Take a look at this TypeScript snippet: import React, { FunctionComponent } from 'react'; export const Chapte ...

Utilizing the axios create method: troubleshooting and best practices

I am attempting to use the axios library in my Next.js app (written in TypeScript) to access a public API for retrieving IP addresses from . In my index.ts file, I have the following code: import axios from "axios"; export const ipApi = axios.cr ...