Is it possible to match different types with specific object keys? Can we determine a type based on neighboring

Query

Can types/interfaces be mapped to match the types of object instances, like a map with objects as keys and types as values?

Demonstration

type StringKeys = {
    foo: "foo" | "Foo",
    bar: "bar" | "Bar"
}

function fooBar<T extends keyof StringKeys>(s: T, t: StringKeys[T]) {}

// Accurately maps types
fooBar('foo', 'Foo') 
fooBar('bar', 'Bar') 
// fooBar('bar', 'foo') ❌ 

/* --- OBJECTIVE --- */
class Buzz{
    name: string

    constructor(name: string) {
        this.name = name;
    }

    greet() {
        console.log(`Hi, I'm ${this.name}`)
    }

    getTitle():string {
        return `Senior commander ${this.name}`
    }
}

class Foo extends Buzz{
    constructor(name: string) {
        super(name);
    }

    getTitle():string {
        return `Professor ${this.name} the Fourth`
    }
}

class Bar extends Buzz{
    constructor(name: string) {
        super(name);
    }

    getTitle():string {
        return `${this.name} the one and only.`
    }
}

type GetValues<T extends Foo|Bar> = T extends Foo ? 'Foo' : T extends Bar ? 'Bar' : never

function fooBarObj<T extends Foo|Bar>(s: T, t: GetValues<T>){};

fooBarObj(new Foo('foo'), 'Foo')
fooBarObj(new Bar('bar'), 'Bar') // <- Error, expects 'Foo'

Playground

Usage Scenario

Similar to the example provided, my intention is to create a function that accepts a parameter which is an instance of one of several sibling classes, and enhances the type of the second parameter based on the specific class of the first parameter.

Answer №1

One of the defining features of TypeScript's type system is its structural nature, where types are compared based on their shape and structure rather than just by name or declaration site. This can be confusing for those accustomed to nominal type systems like Java, where two classes with identical fields and methods would still be considered distinct. In contrast, in TypeScript, if two types have the same structure, they are considered equivalent, even if they are from separate class declarations. While structural typing has many advantages, there are cases where developers may prefer a more nominal approach.

A long-standing request within the TypeScript community is for support for nominal typing, as detailed at microsoft/TypeScript#202. Various techniques have been suggested to emulate nominal typing behavior within TypeScript.


One such technique, outlined in the TypeScript FAQ, involves adding a "brand" member to each type to distinguish them from one another. For example, by including a member named type with a string literal value:

class Foo extends Buzz {

    readonly type = "Foo";
    
    constructor(name: string) { super(name); }   
    getTitle(): string { return `Professor ${this.name} the Fourth` }
}

class Bar extends Buzz {

    readonly type = "Bar";
    
    constructor(name: string) { super(name); }    
    getTitle(): string { return `${this.name} the one and only.` }
}

In this setup, instances of the Foo class have a type property of "Foo", while instances of the Bar class have a type property of "Bar". By utilizing different string values for these properties, Foo and Bar become distinct types, allowing for better differentiation in code.


Another method, specifically applicable to classes, is to declare private or protected members within the classes:

class Foo extends Buzz {

    private nom = true;

    constructor(name: string) { super(name); }   
    getTitle(): string { return `Professor ${this.name} the Fourth` }
}

class Bar extends Buzz {

    private nom = true;
    
    constructor(name: string) { super(name); }    
    getTitle(): string { return `${this.name} the one and only.` }
}

With private or protected members, TypeScript distinguishes between Foo and Bar despite their similar structures, showcasing how visibility modifiers influence type checking in the language.

For more details and to experiment with the code, check out the Playground link.

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

The function `pickupStatus.emit` is not defined

Currently, I have a driver's provider that monitors changes in my firestore database and updates the status of a driver pickup request. Here is the function responsible for this process: getDriverPickupRequest(id) { this.DriverCollection.doc<Driv ...

Retrieve an additional 10 items from the API when the button in the Angular list is clicked

I need to display 10 items each time the button is clicked. Below is the code snippet for the services: import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http' @Injectable({ providedIn: ' ...

Combining two arrays by comparing key values in JavaScript

I am dealing with two distinct arrays. The first one looks like this: const arrOne = [ [{sender: '0000', to: '1111'}], [{sender: '2222', to: '1111'}] ]; And the second array is structured as follows: const a ...

Encountering a Next.js TypeScript Build Error related to the Type 'OmitWithTag<InputFormProps, keyof PageProps, "default">' issue

`I am currently working on a project in Next Js using TypeScript. During the build process with npm run build, I encountered the following errors in the terminal: # Type 'OmitWithTag<InputFormProps, keyof PageProps, "default">' do ...

Angular 4 - Sum all values within a nested array of a data model

I am working with an array of Models where each object contains another array of Models. My goal is to calculate the sum of all the number variables from the nested arrays using the code snippet below. Model TimesheetLogged.ts export interface Timesheet ...

Issue: anticipated ']' after statement in sanity in conjunction with nextjs

Struggling to retrieve data from Sanity in Next.js, but encountering an error that reads: "Error: expected ']' following expression." As a beginner in this, I've been trying to troubleshoot it, but I'm unsure of the root cause of the er ...

Enhance Your GoJS Pipeline Visualization with TextBlocks

I am facing challenges in customizing the GoJS Pipes example to include text within the "pipes" without disrupting the layout. Although I referred to an older response on the same query here, it seems outdated or not detailed enough for me to implement wit ...

Organizing an array based on designated keywords or strings

Currently, I am in the process of organizing my logframe and need to arrange my array as follows: Impact Outcomes Output Activities Here is the initial configuration of my array: { id: 15, parentId: 18, type: OUTPUT, sequence: 1 }, { id: 16, parentId: ...

Angular 2: A guide to resetting dropdown and text values when changing radio button selections

When the user interface displays two radio buttons - one for YES and one for NO - and the user clicks on YES, a dropdown is shown. Conversely, if the user clicks on NO, a textbox is displayed. How can I clear the values in the dropdown and textbox when s ...

An array containing optional types Ts and required types Ks

Can an array be typed in TypeScript to require at least one or more strings at any index, with any extra elements being numbers? type NumberArrayWithAtleastOneString = [...(number[] | string)[], string] const a: NumberArrayWithAtleastOneString = [1,' ...

What is the best method for obtaining XML within typescript react in the bpmn-js/lib/Modeler?

After importing my BPMN XML in Model using importXML and setting bpmnModeler to bpmnModelerClone, I now need to retrieve the BPMN from bpmnModelerClone. How can I achieve this? Below is the code snippet showing how I imported XML and set bpmnModeler to bp ...

Verify registration by sending an email to an alternate email address through Angular Firebase

I have implemented email verification for users before registration. However, I would like to receive a verification email to my own email address in order to finalize the registration process. I want to be notified via email and only after my approval sho ...

Experience feelings of bewilderment when encountering TypeScript and Angular as you navigate through the

Exploring Angular and TypeScript for an Ionic project, I am working on a simple functionality. A button click changes the text displayed, followed by another change after a short delay. I'm facing confusion around why "this.text" does not work as exp ...

Vue + TypeScript prop type issue: "'Foo' is intended as a type, but is being treated as a value in this context."

As a newcomer to TypeScript and the Vue Composition API, I encountered an error that left me puzzled: I have a component that requires an api variable as a prop, which should be of type AxiosInstance: export default defineComponent({ props: { api: A ...

Utilizing the array.map method to access the key of an object within an array of arrays in TypeScript

Can I utilize array.map in TypeScript to apply a function with the parameter being the key of an object within an array? I have an array containing objects which have keys 'min' and 'max'. I am looking to use something like someArrayFun ...

What methods can be implemented to ensure ComponentOverride's universality?

These type definitions for markdown-to-jsx don't seem to be generic enough, causing issues like the one mentioned below. For more details, refer to Why is type SFC<AnchorProps> not assignable to type SFC<{}>? /Users/sunknudsen/Sites/sunk ...

Navigating to a different page in Ionic 2 upon app initialization

Is there a way to automatically redirect the page to the home page instead of displaying the login page if there is already a token stored in localStorage? I currently have the following code in the constructor() of app.component.ts, but it still display ...

Angular2's internationalization feature (i18n) is not compatible with templateUrls that are located outside of the main project directory

Currently, I'm delving into the realm of internationalization in my Angular2 app by following the official guide provided here. The structure of my app is as follows: my-angular2-app | +-- app | | | +-- myComponent | | | | | +-- m ...

Restrictions on pairings of kind variables within generic utilization

Currently, I am creating a declaration file for a library called chart.js. The process of constructing a new chart involves the following: let chart = new Chart(ctx, { type: 'line', data: ..., options: ... }) The types of the data and options f ...

Grab a parameter from the URL and insert it into an element before smoothly scrolling down to that

On a button, I have a URL that looks like this: www.mywebsite.com/infopage?scrollTo=section-header&#tab3 After clicking the button, it takes me to the URL above and opens up the tab labeled tab3, just as expected. However, I would like it to direct m ...