Using a template literal with the keyof operator as an interface property

Imagine a scenario where we have a basic interface:

interface Schedule {
  interval: "month" | "day";
  price: number;
}

We can create a template-literal type with TypeScript version 4.4:

type PrefixedScheduleKey = `schedule__${keyof Schedule}`; // "schedule__interval" | "schedule__price"

If there are multiple interfaces like this and we want to combine (essentially flatten) their prefixed, template-literal keys into something like this:

interface Metadata {
  foo: string;
  bar: string;
  // computed 
  schedule__interval: "month" | "day";
  schedule__price: number;
  ...
}

How do we define such interface keys?

interface Metadata {
  [key: PrefixedScheduleKey]: any
}

// This throws an error
// An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.(1337)

Also,

interface Metadata {
  [key in `schedule__${keyof Schedule}`]: any 
}

// Results in
// A computed property name must be of type 'string', 'number', 'symbol', or 'any'.(2464)
// Member '[key in `schedule__${keyof' implicitly has an 'any' type.(7008)

You can test this on the playground.

By the way, it was mentioned on GitHub that using type instead of interface is recommended, but in our case, Metadata already extends another interface so we stuck with using interface for simplicity.

Answer №1

One cannot directly perform this action on an interface, as detailed in this Typescript: Mapped types with Interface reference.

In TypeScript (up to version 2.7.2), union type signatures within interfaces are not permitted, and instead mapped types should be utilized (as you have correctly done).

Refer to the official documentation.

Nevertheless, it is feasible to achieve this using an intersection type between a random interface and a mapped type, just like in the initial GitHub issue you cited ("Moves other members to a separate object type that gets combined with an intersection type"). You can also employ the as keyword to inline your PrefixedScheduleKey template-literal type while keeping your Key intact for referencing Schedule[Key]. The same method can be applied to other prefixed types, either within the same mapped metadata type or in adjacent ones intersected later.

It's important to note that key must be capitalized as Key when used as a type variable in mapped types, as shown in the mapped types handbook page.

interface BareMetadata /* extends ArbitraryType */ {
  foo: string;
  bar: string;
}

type ScheduleMetadata = {
  [Key in keyof Schedule as `schedule__${Key}`]: Schedule[Key];
}

type MergedMetadata = BareMetadata & ScheduleMetadata;

const example: MergedMetadata = {
  foo: "one",
  bar: "two",
  schedule__interval: "month",
  schedule__price: 9.99
};

Playground Link

You also have the option to extend an object-like type with an interface, which could eliminate the need for explicit intersection:

interface MergedMetadata extends BareMetadata, ScheduleMetadata {}
// or
interface Metadata extends ScheduleMetadata {
  foo: string;
  bar: string;
}

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

Creating dynamic types in TypeScript through existing types

Consider the following object: class Product { constructor( public metadata: Metadata, public topic: Topic, public title = 'empty' ) {} ... } I am looking to define an interface: interface State<T> { } This interface s ...

Encountering the error message 'array expected for services config' within my GitLab CI/CD pipeline

My goal is to set up a pipeline in GitLab for running WebdriverIO TypeScript and Cucumber framework tests. I am encountering an issue when trying to execute wdio.conf.ts in the pipeline, resulting in this error: GitLab pipeline error Below is a snippet of ...

When attempting to assign a 'string' type to the @Input property, I am receiving an error stating that it is not assignable to the 'PostCard Layout' type

Encountering an issue The error message 'Type 'string' is not assignable to type 'PostCard Layout'' is being displayed A parent component named page-blog.component.html is responsible for defining the class styles and passi ...

How can JSON be best connected in Angular for optimal performance?

My JSON structure is as follows: { items:[], errors:[], foundItems:9 } One part of my program requires access to "items", while another part needs access to "errors." To resolve this issue, I decided to create a new interface and a new class to hand ...

How can type annotations be properly incorporated into this TypeScript code using an inline function class?

How can I provide type annotations to inform TypeScript that this code is correct? Indeed, I do require that inline function class. The code presented here is a simplified version of my actual problem. let x = 10; const obj = new (function() { if(--x) r ...

Is there another option for addressing this issue - A function that does not declare a type of 'void' or 'any' must have a return value?

When using Observable to retrieve data from Http endpoints, I encountered an error message stating that "A function whose declared type is neither 'void' nor 'any' must return a value." The issue arises when I add a return statement in ...

Performing a simulated form submission using Angular HttpClient and then redirecting the user after

My current issue involves performing a form POST to a 3rd party payment provider using Angular TypeScript and then redirecting to their hosted payment page. When I submit a standard form via a regular HTML page, the redirection happens automatically. Howev ...

"Encountered an error in Angular: ContentChild not found

Working with Angular 5, I am attempting to develop a dynamic component. One of the components is a simple directive named MyColumnDef (with the selector [myColumnDef]). It is used in the following: parent.compontent.html: <div> <my-table> ...

Guide to creating numerous separate subscriptions in angular 6

Can you explain the differences between flatMap(), switchmap(), and pipe()? Which one would be most suitable for the given scenario? I need to call the next method once both responses are received. this.jobService.getEditableText('admins', compar ...

Checking constructor arguments and code style issues

I need to ensure that the constructor parameter is validated correctly when an instance of a class is created. The parameter must be an object that contains exactly all the properties, with the appropriate types as specified in the class definition. If t ...

What is the proper method for expanding Material-UI components in React using Typescript?

I am currently working on customizing material-ui components by extending them as my own with unique properties, using reactjs paired with typescript. Here is the snippet of code that I have been experimenting with: import React from 'react'; i ...

Triggering two function calls upon submission and then waiting for the useEffect hook to execute

Currently, I am facing a challenge with form validation that needs to be triggered on submit. The issue arises as some of the validation logic is located in a separate child component and is triggered through a useEffect dependency from the parent componen ...

populate a data list with information sourced in Angular 8

I am looking to populate this model oldDataSource: Array<any> = []; with the values from the datasource. This is the code I have tried: ngOnInit(): void { this.dataSourceInit(); } dataSourceInit(): void { this.dataSource = new DefaultScore ...

Managing Visual Studio Code Extension Intellisense: A Guide

I am looking to create an extension I recommend using "CompletionList" for users. Users can trigger completion by running "editor.action.triggerSuggest" The process of my extensions is as follows: Users input text If they press the "completion command," ...

Tips for organizing a multi-layered object based on an internal value

I am currently facing a challenge in sorting a complex object. Here is the structure of the object: [{ "searchResultProperties": [{ "key": "message_time", "value": 1542088800000 }, { "key": "size_byte AVG", "value": ...

Tips for transferring items between two .ts files

Currently, in my code file numberOne.ts, I am making an API call and receiving a response. Now, I want to pass this response over to another file called numberTwo.ts. I have been attempting to figure out how to transfer the API response from numberOne.ts ...

Error NG8001: The element 'router-outlet' is not recognized: Make sure that 'router-outlet' is a component in Angular, and confirm that it is included in this module

Having trouble running ng serve or ng build due to an error message stating that 'router-outlet' is not a recognized element. The Angular application structure looks like this: app.module.ts: import { NgModule } from '@angular/core'; i ...

Alias for function in TypeScript declaration file (.d.ts)

There is a function within a Node module that I am trying to document in a .d.ts file. This function has two aliases, config() and load() (check the source here). The function definition in the dotenv/index.d.ts file looks like this: export function confi ...

Tips for storing the device token received from Firebase Cloud Messaging in an Ionic2 application

Using the FCM plugin for ionic2, I was able to successfully implement push notifications. For reference, you can check out the plugin here. I followed the steps outlined in this Github repository, and everything is working smoothly so far. Now, my next go ...

Guide on creating a custom type for an object utilizing an enum framework

Enumerating my shortcuts: export enum Hotkey { MARK_IN = 'markIn', MARK_OUT = 'markOut', GO_TO_MARK_IN = 'goToMarkIn', GO_TO_MARK_OUT = 'goToMarkOut' } I am now looking to define a type for a JSON ob ...