What is the best way to reference a map key within a map value?

In my code, I have a specific type defined as follows:

type mapOptions = {
   'a': {},
   'b': {
      'somethingElse': string,
      'somethingDiff': number
   },
   'c': {
      'somethingC': string
   }
}

Now, what I am aiming to do is create a Map where the keys correspond to the keys of my object, and each value corresponds to a particular object structure, like this:

type innerMapObject<T extends keyof mapOptions> {
     status: boolean,
     options: mapOptions[T]
}

The main goal here is to ensure that when interacting with the map - whether fetching or setting values - the correct underlying option types are enforced:

const MyMap: Map<...> = new Map();

MyMap.set('d', {}); // This should trigger an error since "d" is not a property within "mapOptions"
MyMap.set('a', {}); // This operation should work without any issues
MyMap.set('c', {
      "somethingD": "test"
}); // Here, we expect an error because the value object does not align with "mapOptions["c"]"

/** The expected type for myBValue should be:
 *
 *  {
 *      status: boolean,
 *      options: {
 *         somethingElse: string,
 *         somethingDiff: number
 *      }
 *  }
 *
 *
 */
const myBValue = MyMap.get("b");

Are there any possible solutions to reference the key of the map within the associated value?

Answer №1

A bit strange coding-wise, but yes, it's possible. The Map type in TypeScript requires two generics: K for key and V for value. This is similar to using Record<K, V>. However, when working with objects, it can be challenging to specify distinct values for specific keys due to all keys having the same value type.

Fortunately, there is a workaround that involves creating an intersection of multiple Maps to help TypeScript understand overloaded signatures and allow for specific pairing of keys and values.

type MyMap = Map<"planet", { name: string; size: number }> & Map<"person", { name: string; age: number }>;
const myMap: MyMap = new Map();

// these work 🎉

myMap.set("planet", {
    name: "Mars",
    size: 21,
});

myMap.set("person", {
    name: "Jon Doe",
    age: 21,
});

// these fail 🎉

myMap.set("invalid_key", 2);

myMap.set("person", {
    name: "Hey",
    size: 2, // should set `age` instead of `size`
});

To simplify this process, I've created a helper type that generates a Map<K, V> from an object type. It accepts keys and values as an array of tuples in the format [key, value]. Using arrays allows for various key types, unlike regular objects which are limited to PropertyKey (string | symbol | number).

// Link to Playground for testing

Edit: Explanation

My solution leverages distributive types. In the CreateMap type, we define keys and values as tuples:

// Example usage of CreateMap type

By processing each entry in the tuple separately, we ensure accurate mapping of keys to corresponding values. This approach guarantees specificity in key-value pairs within the map.

Using UnionToIntersection, we merge individual maps into one, ensuring all specified key-value patterns coexist harmoniously.

// Further explanation on conditional types and UnionToIntersection

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

What is the process for refreshing one observable with data from another observable?

As a complete beginner to these technologies, please bear with me if my question sounds strange and the terminology is not quite right. I have a component that displays data in a table format. export class SourceFieldComponent implements OnInit { ... ...

The React.FC component encountered an invalid hook call

I've encountered an issue while using TypeScript and trying to implement a hook within React.FC. Surprisingly, I received an error message stating that hooks can only be used inside functional components. But isn't React.FC considered a functiona ...

Utilizing Class Types in Generics with TypeScript

Struggling to implement a factory method based on the example from the documentation on Using Class Types in Generics, but hitting roadblocks. Here's a simplified version of what I'm attempting: class Animal { legCount = 4; constructor( ...

Ways to exclude the optional parameter function

My issue lies not with optional primitive parameters, but with optional functions. For example, say I have a function that requires one parameter and one optional parameter. function performTask(input: string, callback?: () => void){ let temp = input ...

Learn how to render a single element with multiple child elements within separate `<td>` tags in a table row using React

I'm just starting out with React and I have a code snippet that I'm using to render an HTML table in a component. Is there a more optimized way to achieve this? bodyItems = sorted.map((data) => [ data.employerName, data.sectors.map((sector ...

The 'cookies' property is not defined in the 'undefined' type

I am working on incorporating Google's Sign-In for Cypress tests using the following plugin: https://github.com/lirantal/cypress-social-logins/ (I am utilizing TypeScript). The code I have implemented is as follows: it('Login through Google&apos ...

Invoke a function of a child component that resides within the <ng-content> tag of its parent component

Check out the Plunkr to see what I'm working on. I have a dynamic tab control where each tab contains a component that extends from a 'Delay-load' component. The goal is for the user to click on a tab and then trigger the 'loadData&apo ...

Collaborate and reuse Typescript code across various Node projects

Imagine we have a project structured like this: webapps ProjectA SomeClass.ts Package.json ProjectB SomeClass.ts Package.json Common LoggingClass.ts Package.json The Common "LoggingClass" needs to import a module from NPM. Let's say that ...

Setting the current time to a Date object: A step-by-step guide

Currently, I am working with a date input and storing the selected date in a Date object. The output of the date object looks like 2021-03-16 00:00:00. However, I want to update this date object's time part to reflect the current time. The desired ou ...

Tips for eliminating contenthash (hash) from the names of JavaScript and CSS files

Google's cached pages are only updated once or twice a day, which can result in broken sites on these cached versions. To prevent this issue, it is recommended to remove the contenthash from the middle of the filename for JavaScript files and eliminat ...

Error message: "The overload signature does not match the function implementation in Typescript class"

Below is the class that I have created: export class MyItem { public name: string; public surname: string; public category: string; public address: string; constructor(); constructor(name:string, surname: string, category: string, address?: ...

Having trouble uploading images using Ionic/Angular to a PHP script

I've been working on incorporating image uploading functionality into my Ionic app. Despite reading multiple tutorials, I haven't been able to get it up and running successfully. I'm specifically aiming for the app to work smoothly in a web ...

Angular2 - The Iterable Differ fails to detect changes

I am currently utilizing the Iterable Differs feature in Angular2 to monitor changes in my data. However, I am facing an issue where the differ.diff method always returns "null" and I am unsure of the reason behind this. constructor(differs: IterableDiffe ...

Using Angular: Passing a service as a parameter

I have a desire to improve the organization of my code by moving certain functions into an external file and accessing them from multiple components. These functions rely on a service: The MyFunctions Class import { Service1 } from "../_services&quo ...

The incorrect starting points for nested for loops

I'm facing an issue while utilizing a nested for loop to generate x and y coordinates for a method call. Strangely, the loop variables seem to be starting off at incorrect values when I check using console.log. What could be the reason behind this une ...

Ways of invoking a component method from a service in Angular 2

I have a concept for creating a unique service that is capable of interacting with one specific component. In my application, all other components should have the ability to call upon this service and then have it interact with the designated component in ...

Angular material dialog box experiencing issues with saving multiple values

Currently, I am utilizing Anular9 and implementing mat-raised-button in the following manner: <button mat-raised-button (click)="saveClick()" color="primary"><mat-icon>check</mat-icon>&nbsp;&nbsp;Ok</butto ...

AWS Lambda Tutorial: Handling Arguments in Handler Class

I have a straightforward lambda function that is working correctly. However, when I attempted to test it using Jest version 29, my test file returned an error: It says that an argument for 'context' was not provided. Here is the code for my Lam ...

What is the method for utilizing Tuple elements as keys in a Mapped Type or template literal within typescript?

Is there a specific way to correctly type the following function in TypeScript? Assuming we have a function createMap() that requires: a prefix (e.g. foo) and a tuple of suffixes (e.g. ['a', 'b', 'c']) If we call createMap(& ...

Implementing Dynamic FormControl Values within FormGroup in Angular: A Step-by-Step Guide

GenerateFields(customControl, customValue): FormGroup { return this.fb.group({ customControl: new FormControl(customValue), }) } I am looking for a way to dynamically add the value of customControl from the parameter passed in the Ge ...