Creating a versatile TypeScript interface that can accurately represent a wide range of types, interfaces, and objects whilst imposing restrictions on the allowable value types within

I am looking to define a versatile TypeScript interface that can accommodate any type, interface, or object while imposing restrictions on the types of values it contains.

Let me introduce MyInterface, which includes properties fooIProp and barIProp storing strings with an example:

interface MyInterface {
  fooIProp: string;
  barIProp: string;
};

const testInterface: MyInterface = {
  fooIProp: "foo",
  barIProp:  "bar"
};

Next is the MyType type alias, featuring properties fooTProp and barTProp holding strings as shown here:

type MyType = {
  fooTProp: string;
  barTProp: string;
}

const testType: MyType = {
  fooTProp: "foo",
  barTProp: "bar"
}

Here's an object with properties fooObjectKey and barObjectKey keeping strings:

const testObject = {
  fooObjectKey: "foo",
  barObjectKey: "bar"
}

I have devised MyGenericInterface that accepts objects containing only strings for keys and values:

interface MyGenericInterface { [key: string]: string }

When trying to assign testInterface to MyGenericInterface,

const testFromInterface: MyGenericInterface = testInterface;
const testFromType: MyGenericInterface = testType;
const testFromObject: MyGenericInterface = testObject;

A TS2322 error is thrown:

Type 'MyInterface' is not assignable to type 'MyGenericInterface'.
  Index signature is missing in type 'MyInterface'.(2322)

Visit the TypeScript Playground for more insights.

Question: How can I develop a generic TypeScript interface that supports various types/interfaces/objects while controlling the types of values within?

Answer №1

Presently, there is a recognized issue where implicit index signatures are not automatically included in values of an interface type, unlike in anonymous object types. For more details, refer to this source and the discussion on microsoft/TypeScript#15300.


The reason behind this behavior stems from the fact that TypeScript object types are extendible/open rather than closed/exact, as explained further in microsoft/TypeScript#12936. This allows for properties beyond those explicitly defined in an interface.

const someObject = {
  fooIProp: "foo",
  barIProp: "bar",
  baz: 12345
};
const unexpectedMyInterface: MyInterface = someObject; // no error

Although warnings may be raised regarding excess properties when directly adding unknown ones to an annotated object literal, giving a sense of exactness to object types in TypeScript even though they are not truly exact.

Assigning unexpectedMyInterface to MyInterface but not to MyGenericInterface adheres to TypeScript's principles, preventing incorrect assignments. The same logic applies when dealing with MyType:

const someOtherObject = {
  fooTProp: "foo",
  barTProp: "bar",
  baz: 12345
};
const unexpectedMyType: MyType = someOtherObject;

However, assigning unexpectedMyType incorrectly to MyGenericInterface is permissible:

const badMyGenericInterface: MyGenericInterface = unexpectedMyType;
console.log(badMyGenericInterface.baz?.toUpperCase()); // no error in TS, but 
// RUNTIME 💥 badMyGenericInterface.baz.toUpperCase is not a function

This decision seems to reflect a tradeoff between safety and usability, favoring allowing such actions for anonymous object types over interface types due to potential future augmentations or mergers.

If you wish to advocate for extending implicit index signatures to interface types as well, show your support on microsoft/TypeScript#15300.


Until any changes are made, workarounds like using mapped types to convert MyInterface to its equivalent object type before assignment can be utilized:

type Id<T> = { [K in keyof T]: T[K] } // change interface to mapped type version
type MyInterfaceAsType = Id<MyInterface>;
const testFromInterface2: MyGenericInterface = testInterface as MyInterfaceAsType
const testInterfaceAnonymousTypeVersion: MyInterfaceAsType = testInterface;
const testFromInterface3: MyGenericInterface = testInterfaceAnonymousTypeVersion;

While other solutions exist, their suitability may depend on specific use cases. Where are you encountering issues that require resolution?


For interactive exploration, here's a link to the code on the TypeScript playground: https://www.typescriptlang.org/play?target=7#code/JYOwLgpgTgZghgYwgAgLIE8CS5rycgbwChlkYB7czABSnIAcAuZAZzClAHMBuE5AIzhQadJq3ZdeAX15EE5EG2SQ22SLEQRmGNbk3IAvIT4UqtBswBEpywBo+g4ebGXHlojKJEw6eigwAKr4oRsSkpgHOzGwcIDwOQpGi0RJx0l7yimDKEGxBftro+SHG4ZRJFsjWlHYJUBUubh4ZCkoqYADy-ABWEAjZocgmlF29-QDSEOhWNvakjqN9YJPTVU1SXqDqeP7oAOIQINDACLoa+ATIANoA1lMpsZwAug9cyBstWazkALYQi-1DKUyJQRJVquRavMhGDGkIoQI4AAvZgARgATABmAAsAFYPLxMkoAK5HAAefn6EAAJjocOctGgsPSdkCWL9-j0ltxkAB6XnIEDkZDQOhQLw+PxoTBAunbTSfJTsv4dMAAC2gAIGwIiUSqszqDSsbjmiJRyAxOPxniJ2VJEApSxpgWChWKbI5qo1UC1hNa2UEtP2h2OpxZmkKByOHDD8vwRntjqpQeKfsU5AANhAAHQZ8icAAUgYwUdDZx22cESIA-NmwOQAKr0PxQADCcBYEALAEpuzz+YLhaLyFBkKBkAEAMq2ATE7JEAcAJQbADkAphUABRZCAXg3AKU7iKDpZj5c0leRdcbzeg7c7Y5Yg+ycDIpP6wAUiuy7QAYnQfqekEjEMT3DeMclUUCIDTNpcjAX9fmKIDoxOACSnaVM5H9cC4L-LUkLLSCgXaX0JWCZBMGpAAeAIAD4gUuK5xjHEBkDudByBgCcXgnRinnePkBQQNU4DiFAtj0fB62QH44GvallDIgA3aAWHfEBvDIuUJIgABBFh3SMCjKK0hkaOgr9YPg-9IPRfCQLjNDYNQ5AOyZVC9OKTCvnadyhRAdAfnIYl9OCAA1FS1MKdyQqlIwfMg8zsKs1DMTslDCLipzIJ0vyAqCmKIHCqBVIUXggA

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

Arranging Check Boxes Side by Side

I am struggling to properly align the three checkboxes and tables next to each other. I have managed to get the checkboxes on the same row, but aligning them neatly has proven to be a challenge. Using Notepad++ as my development tool, I am facing formattin ...

I'm baffled by the unexpected result I'm getting when using 'foreach' in node.js

Recently delving into node.js, I've encountered a puzzling issue. I'm perplexed as to why my output appears as: ciao data data instead of: data data ciao Below is the code causing this unexpected output: fs.readdir("sender", (err, fil ...

A step-by-step guide on incorporating the C3 Gauge Chart into an Angular 5 application

I have been attempting to incorporate the C3 Gauge Chart from this link into a new Angular 5 application. Below is my code within chart.component.ts : import { Component, OnInit, AfterViewInit } from '@angular/core'; import * as c3 from &apos ...

Tips for preventing the caret (^) symbol from being added to a package when installing with yarn

Is there a way to prevent Yarn from adding the caret prefix to version numbers when installing packages like npm has for changing version prefix (https://docs.npmjs.com/misc/config#save-prefix)? I would like to apply this configuration only for the current ...

Delving into the World of ES6 Symbols

Throughout my experience with various programming languages, from C# to Lisp to Scala to Haskell, symbols have consistently behaved as singleton objects. This means that any two symbols with the same name are guaranteed to be identical. In Racket: (equal? ...

Issue with Redis cache time-to-live not adhering to set expiration

I have encountered an issue while using IoRedis and DragonflyDB to implement rate limiting in my web application. Despite setting a TTL of 5 seconds for the keys in the Redis DB, sometimes they do not expire as expected. I am struggling to understand why t ...

Is there a way to select an element within a nested list item only when the preceding list item is clicked using Jquery/JavaScript?

I am working with an unordered list that looks like this: <ul> <li><a class="foo" id="one">some text</a></li> <li><a class="foo" id="two">some text</a></li> <li><a class="foo" id="thr ...

Implementing JSON response in email functionality using Ajax and PHP

I've encountered a problem with my contact form. I'm receiving error messages for invalid email and incomplete fields, but I'm not getting a success message. As a result, the email is being sent twice without any confirmation message (I&apos ...

Using Node.js and Express for redirecting to a custom URL

Having an issue with redirection on my small nodejs/express app. The goal is to redirect to an external URL with input values from a form after submitting. index.html <form method="POST" action="https://192.0.2.1/abc.html"> <input name="name ...

The function findOne from Mongoose seems to be non-existent, all while utilizing the Passport library

Whenever I try to implement my local strategy using the passport lib, I keep encountering this error. TypeError: UserModel.findOne is not a function I've spent hours searching for a solution that addresses this issue but haven't been successful ...

The current context does not have a reference to TextBox

I am encountering an issue with my simple aspx page. Despite not using Master Content Page, I am seeing the following error: The name 'txtFirstName' does not exist in the current context Here is my Markup: <%@ Page Language="C#" %> &l ...

Incorporating an HTML/Javascript game into a reactJS website: A step-by-step

After developing a game using plain javascript and HTML along with a few JS libraries, I find myself inquiring about the process of integrating this game into my ReactJS website. Typically, the game is initiated by opening the index.html file located with ...

Implementing an All-Routes-Except-One CanActivate guard in Angular2

In order to group routes within a module, I am aware that it can be done like this: canActivate: [AuthGuard], children: [ { path: '', children: [ { path: 'crises', component: ManageCrisesComponent }, ...

I need to fetch data from mongoDB by allowing the user to input a name into a search field, and then retrieve all documents that correspond to that search term

I am currently able to query the database by finding a specific key:value pair in the documents. However, I would like to enhance this functionality by allowing users to input their own search criteria as an argument in the function. Right now, I have hard ...

What is the best way to retrieve the UTC value of a specific date and time within a particular time zone using JavaScript?

I want to create a Date object with a specific time: "Midnight in Los Angeles on Christmas 2011". Although I've used moment.js, which is good, and moment-timezone, which is even better, neither the default Date class nor moment constructors allow for ...

Having trouble initiating the server using npm start

Just starting out with nodeJs: I have created a server.js file and installed nodejs. However, when I try to run "npm start", I encounter the following errors. C:\Users\server\server.js:43 if (!(req.headers &amp;& req.headers. ...

Instructions on activating dark mode with the darkreader plugin on a Vue.js website

Is there a way to implement DarkMode in a Vue.js application? I attempted to integrate darkmode using this npm package, but I kept encountering the error message: DarkMode not defined. ...

Changing an object received as a prop in Vue.js

Is it acceptable to mutate values in a prop when passing an Object reference? In the process of developing a web application that involves passing numerous values to a component, I am exploring the most efficient method of handling value passing between c ...

Utilizing the 'create' function in sqlite each time I need to run a query

I've been diving into SQLite within the Ionic framework and have pieced together some code based on examples I've encountered. import { Component } from '@angular/core'; import { IonicPage, NavController, NavParams } from 'ionic-a ...

Discover the method for concealing a button using ng-show and ng-hide directives

<div> <div class="pull-right"> <button type="button" data-ng-click="editFigure()" id="EditFigure">Edit Figure </button> <button type="button" data-ng-click="figurePreview()" id="PreviewFigure">Figure Previ ...