I have a question about TypeScript mapped types. Why is it not possible to retrieve the keys of a union of interfaces?

Below is the code snippet that I am working with:

interface Data {
  A: {
    a1: string;
    a2: string;
  };
  B: {
    b1: number;
    b2: string;
  };
}


type TransformDataKey<V extends string, T extends string> = `--${V}-${T}`;
type TransformData<
  V extends string,
  K extends keyof Data,
  D = Data[K]
> = {
  [k in TransformDataKey<V, keyof D>]: string | number; // <-- error on "keyof D"
};

type K = TransformData<'abc', 'A'>; // <-- this is correct

Upon trying to access keyof D in the specified line, an error occurs:

Type 'keyof D' does not satisfy the constraint 'string'. Type 'string | number | symbol' is not assignable to type 'string'. Type 'number' is not assignable to type 'string'.ts(2344)

This error arises because keyof D resolves to never since A and B do not have any common fields. But why is it resolving to string | number | symbol when none of the keys are numbers or symbols?

On the other hand, the type K successfully resolves to:

type K = {
  --abc-a1: string | number;
  --abc-a2: string | number;
}

Is there a correct way to achieve this kind of mapping?

Answer №1

As of Typescript 2.9, the keyof operator now also supports number and symbol named properties, according to official documentation.

Referencing a similar thread on Stack Overflow:

The reason behind this behavior is that JavaScript automatically converts numbers to strings when indexing an object.

Based on the suggestions provided in the above answer, the following code snippet should work effectively:

interface Data {
  A: {
    a1: string;
    a2: string;
  };
  B: {
    b1: number;
    b2: string;
  };
}


type TransformDataKey<V extends string, T extends string> = `--${V}-${T}`;
type TransformData<
  V extends string,
  K extends keyof Data,
  D = Data[K]
> = {
  [k in TransformDataKey<V, Extract<keyof D, string>>]: string | number; //no error
};

type K = TransformData<'abc', 'A'>; // <-- this is correct

Alternatively, you can modify the code as follows to ensure that keys of type number are not ignored:

interface Data {
  A: {
    "a1": string;
    2: string;
  };
  B: {
    b1: number;
    b2: string;
  };
}


type TransformDataKey<V extends string, T extends string | number> = `--${V}-${T}`;
type TransformData<
  V extends string,
  K extends keyof Data,
  D = Data[K]
> = {
  [k in TransformDataKey<V, Extract<keyof D, string | number>>]: string | number;
};

type K = TransformData<'abc', 'A'>;

However, it seems challenging to handle symbols within this context due to restrictions on string interpolation with symbols in JavaScript.

const fooSymbol = Symbol('foo');
const test = `--${fooSymbol}`; // Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'.

Running the code may result in a runtime error:

Cannot convert a Symbol value to a string

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

I'm experiencing some difficulties utilizing the return value from a function in Typescript

I am looking for a way to iterate through an array to check if a node has child nodes and whether it is compatible with the user's role. My initial idea was to use "for (let entry of someArray)" to access each node value in the array. However, the "s ...

Ways to access configuration settings from a config.ts file during program execution

The contents of my config.ts file are shown below: import someConfig from './someConfigModel'; const config = { token: process.env.API_TOKEN, projectId: 'sample', buildId: process.env.BUILD_ID, }; export default config as someCo ...

What is the correct way to destructure a tuple in Typescript when only certain values will be assigned to new variables?

When writing code, I frequently encounter situations that resemble the following: function example(parameter: string) { const tuple = [ "newParameterValue", "newVariableValue" ] let newVar; [parameter, newVar] = tuple; } ( ...

How can I get rid of the table borders and set colors for every other row in the

How can I remove the border lines from a table and assign colors to alternate rows in the table? Follow this link for the code: https://stackblitz.com/angular/kooxxyvddeqb?file=app%2Ftable-sticky-columns-example.css Thank you in advance ...

How can you alter the background color of a Material UI select component when it is selected?

I am attempting to modify the background color of a select element from material ui when it is selected. To help illustrate, I have provided an image that displays how it looks when not selected and selected: Select Currently, there is a large gray backgr ...

Guide on changing the background image of an active thumbnail in an autosliding carousel

My query consists of three parts. Any assistance in solving this JS problem would be highly appreciated as I am learning and understanding JS through trial and error. https://i.sstatic.net/0Liqi.jpg I have designed a visually appealing travel landing pag ...

The wrong parameter is used to infer the generic function type argument in TypeScript

When using the code snippet below and not explicitly specifying T at function call, such as getOrPut<Item>(...), it is inferred from the create parameter. However, this can lead to the created item type being incompatible with the obj dictionary, as ...

Unlocking the power of variables in Next.js inline sass styles

Is there a way to utilize SASS variables in inline styles? export default function (): JSX.Element { return ( <MainLayout title={title} robots={false}> <nav> <a href="href">Title</a> ...

Ways to display "No records" message when the filter in the material table in Angular returns no results

How can I implement a "No Records Message" for when the current table is displaying empty data? Check out this link for examples of material tables in AngularJS: https://material.angular.io/components/table/examples ...

TypeScript interface with an optional parameter that is treated as a required parameter

Within my interface, I have a property that can be optional. In the constructor, I set default values for this property, which are then overridden by values passed in as the first parameter. If no properties are set, the defaults are used. I am looking fo ...

Encountering the "ExpressionChangedAfterItHasBeenCheckedError" in Angular 2

As I try to fill in multiple rows within a table that I've created, the table gets populated successfully. However, an error message pops up: "ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous valu ...

Angular 17 isn't notifying child component of signal changes

In the statistics module, I have a signal that specifies the type of charts to display and this signal is updated through a radio button group. The signal: typeSignal = signal<string>('OIA') The radio buttons for setting the : <div clas ...

Having an issue where the Material Angular 6 DatePicker is consistently displaying my selected date as one day earlier

I've encountered a strange issue with the current version of the Material Angular DatePicker. After upgrading from A5 to A6, it started to parse my date one day earlier than expected. You can see an example of this problem here: https://stackblitz.com ...

Bringing in TypeScript definitions for gridster

After starting a new ionic project, I decided to include the gridster.js library by running npm install gridster and npm install @types/jquery.gridster in the root directory of my project. However, when trying to import the installed definitions, I encount ...

What is the method for copying table data, including input text as cells, to the clipboard without using jQuery?

I have a basic table that looks like this: <table> <thead> <tr> <th>Savings</th> </tr> </thead> <tbody> <tr> <td>Savings <button type="button" (click)=" ...

Steps to Transform String Array into Prisma Query Select Statement

I have a requirement to dynamically select Prisma columns based on client input: ['id', 'createdAt', 'updatedAt', 'Order.id', 'Order.Item.id', 'Order.Item.desc'] The desired format for selection ...

Singleton constructor running repeatedly in NextJS 13 middleware

I'm encountering an issue with a simple singleton called Paths: export default class Paths { private static _instance: Paths; private constructor() { console.log('paths constructor'); } public static get Instance() { consol ...

Declaration in Typescript for an array of strings that will be returned as a

I am facing an issue with my async function that is supposed to return either a single string or an array of strings. Here is the relevant code snippet: async getAllAnnotationTimes(): Promise<string> | Promise<string[]> { return aw ...

What is the process of converting the new syntax of SomeFunction() to TypeScript?

When I try to convert a basic JS file to TS while having implicit "any" disabled, I encounter the following error: Error TS7009: When attempting to create a new expression without a constructor signature, it implicitly defaults to an 'any' typ ...

How can we enhance intellisense to recognize instance members of an interface when using dynamic indices?

In the midst of developing an angular project, I am currently utilizing an interface to specify a configuration for a module. The design of the interface revolves around mapping names to objects and is relatively straightforward: export interface NamedRou ...