Utilizing type maps within nested objects in Typescript: A comprehensive guide

Initially, a type is established that connects enum keys with specific types:

enum MyEnum {
    A,
    B
}

type TypeMap = {
    [MyEnum.A]:string,
    [MyEnum.B]:number
}

interface ObjInterface<T extends keyof TypeMap> {
    obj: T,
    objData: TypeMap[T]
}

interface SecondaryInterface {
    value: string,
    objChosen: ObjInterface<keyof TypeMap>
}

Subsequently, an object is created where the type of objData is checked against the TypeMap:

myObj:SecondaryInterface = {value:"", objChosen:{obj:MyEnum.A, objData:"a string"}}

Although this approach partially functions, when typing objData, it displays a union type hint 'string | number' instead of just 'string' because it deduces the type based on keyof TypeMap rather than the exact TypeMap[T].

Is there a way to achieve an exact type match for the enum key used and its corresponding type defined in the type map?

While a type assertion can make it work, is there a way to achieve this without one?:

myObj:SecondaryInterface = {value:"", objChosen:<ObjInterface<MyEnum.A>>{obj:MyEnum.A, objData:"a string"}}

Answer №1

As per your explanation, the

ObjInterface<MyEnum.A | MyEnum.B>
represents a single object type where obj can be any MyEnum and objData can be either a string or a number. However, this is not the desired behavior. You would prefer ObjInterface<T> to be distributed over unions in T, so that
ObjInterface<MyEnum.A | MyEnum.B>
is interpreted as
ObjInterface<MyEnum.A> | ObjInterface<MyEnum.B>
. Different methods can achieve this, like employing distributive conditional types or crafting a distributive object type as described in the pull request microsoft/TypeScript#47109. This approach involves indexing into a mapped type.

If you have a keylike type K and wish to distribute the type function F<K> over unions in K, you can write {[P in K]: F<P>}[K]. This results in a mapped type with one property for each member of K, which is immediately indexed to generate a union of those properties.

Your code snippet looks like this:

type ObjInterface<K extends keyof TypeMap> = {
  [P in K]: {
    obj: P,
    objData: TypeMap[P]
  }
}[K]

Thus,

ObjInterface<keyof TypeMap>
resolves to

/* type ObjInterface<keyof TypeMap> = {
    obj: MyEnum.A;
    objData: string;
} | {
    obj: MyEnum.B;
    objData: number;
} */

Since this technically isn't an interface anymore, you might want to consider changing the name.


At this point, you haven't provided a compelling reason to keep ObjInterface as a generic type, especially if you only intend to use keyof TypeMap. If the generic isn't necessary, you can set K to be keyof TypeMap and define it as

type ObjInterface = { [K in keyof TypeMap]: {
  obj: K,
  objData: TypeMap[K]
} }[keyof TypeMap]

Subsequently, the rest of your code can be written as

interface SecondaryInterface {
  value: string,
  objChosen: ObjInterface
}

const myObj: SecondaryInterface =
  { value: "", objChosen: { obj: MyEnum.A, objData: "a string" } }; // okay
myObj.objChosen = { obj: MyEnum.B, objData: 123 }; // okay
myObj.objChosen = { obj: MyEnum.A, objData: 123 }; // error

as you desire.

Link to Playground to test code

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

Rearrange the layout by dragging and dropping images to switch their places

I've been working on implementing a photo uploader that requires the order of photos to be maintained. In order to achieve this, I have attempted to incorporate a drag and drop feature to swap their positions. However, I am encountering an issue where ...

"Encountering a TypeScript error when using React Query's useInfiniteQuery

I am currently utilizing the pokeApi in combination with axios to retrieve data import axios from 'axios' export const fetchPokemonData = async ({ pageParam = "https://pokeapi.co/api/v2/pokemon?offset=0&limit=20" }) => { try ...

Angular2: Ensuring Sequential Execution Line by Line - A Comprehensive Guide

I have a designed an Angular2 Navbar Component that features a logout button: import { Component, OnInit } from '@angular/core'; import { LoginService } from '../login.service'; import { Router } from '@angular/router'; @Co ...

Retrieve an instance of the property class within a property decorator

I'm attempting to create a decorator called @Prop that will assist me in adjusting attributes for custom elements. This is the desired code: class MyCustomClass extends HtmlElement { get content() { return this.getAttribute('content&apo ...

How can I use Angular2 to draw a square [input] number of times?

I need to create a specific number of squares within a container using Angular2. Can you please provide me with a straightforward example, preferably utilizing canvas? Currently, I have converted webpack starter for use with Angular: This is the code ins ...

Having trouble with ngx-pagination's next page button not responding when clicked?

I am experiencing issues with pagination. The next page button does not function as expected, and clicking on the page number also does not work. Below is the code snippet and a Demo link for your reference. HTML <table mat-table [dataSou ...

Transforming a flat TypeScript array into a nested object structure

I'm working on implementing a user interface to offer a comprehensive overview of our LDAP branches. To achieve this, I plan to utilize Angular Materials Tree as it provides a smooth and intuitive browsing experience through all the branches (https:// ...

import error causing an angular application to crash even with the module installed

Is there a possibility that an error is occurring with the import statement even though the syntax is correct and the required library has been installed? Could the issue lie within the core settings files, specifically the ones mentioned below (package.js ...

React TypeScript - creating a component with a defined interface and extra properties

I'm completely new to Typescript and I am having trouble with rendering a component and passing in an onClick function. How can I properly pass in an onClick function to the CarItem? It seems like it's treating onMenuClick as a property of ICar, ...

Tips for creating a carousel with Angular 9 to showcase numerous items

I've got this code snippet that I'm working on. I want to incorporate a Carousel feature using Angular 9 without relying on any external libraries. Currently, all the data items are appearing in a single row (they are exceeding the specified bor ...

What is the process of extracting information from a JSON file and how do I convert the Date object for data retrieval?

export interface post { id: number; title: string; published: boolean; flagged: string; updatedAt: Date; } ...

Looking to display a single Angular component with varying data? I have a component in Angular that dynamically renders content based on the specific URL path

I have a component that dynamically renders data based on the URL '/lp/:pageId'. The :pageId parameter is used to fetch data from the server in the ngOnInit() lifecycle hook. ngOnInit(){ this.apiHelper.getData(this.route.snapshot.params.pageId) ...

Issue with manipulating currency conversion data

Currently, I am embarking on a project to develop a currency conversion application resembling the one found on Google's platform. The main hurdle I am facing lies in restructuring the data obtained from fixer.io to achieve a similar conversion method ...

Leveraging IntersectionObserver to identify the video in view on the screen

Our Objective I aim to implement a swipe functionality for videos where the URL changes dynamically based on the ID of the currently displayed video. Challenges Faced Although I managed to achieve this with code, there is an issue where the screen flashe ...

Testing exception - unstable FlushDiscreteUpdates

I am currently working on a test using Jest and react-testing-library for a .tsx test file written in TypeScript: import React from "react"; import { Provider } from 'react-redux'; import { render, screen } from "@testing-library/r ...

Angular: Extracting a String from an Observable of any Data Type

Currently, I have a backend REST service that is responsible for returning a string: @GetMapping("/role/{id}") public String findRole (@PathVariable("id") String username) { User user = userRepository.findByUsername(username); return user.getR ...

Encountering difficulties while utilizing Ramda in typescript with compose

When attempting to utilize Ramda with TypeScript, I encountered a type problem, particularly when using compose. Here are the required dependencies: "devDependencies": { "@types/ramda": "^0.25.21", "typescript": "^2.8.1", }, "dependencies": { "ramda ...

Tips for passing the indexes of an array within nested ngFor loops in Angular

I have a 2D grid in my component that is created using nested ngFor loops, and I want to make certain grid elements clickable under specific conditions so they can call a function. Is there a way for me to pass the index of the clicked array element to the ...

What is the best way to pass createdDt and updatedDat values in an Angular form without displaying them on the template?

I am working on a message creation form in Angular where I need to include createdDt and updatedDt as hidden values. These values should not be visible in the template. I want to automatically set the current date and time for both createdDt and updatedD ...

What is the best way to retrieve a property from a conditional type using generics?

The code snippet above presents an issue in TypeScript: const exampleFn = function<AttributeName extends 'attributeA' | 'attributeB'>( whatToProcess: AttributeName extends 'attributeA' ? {attributeA: string} : {attri ...