Ensure that every member of an object adheres to a particular generic interface in some capacity

Can a TypeScript index signature type be used to enforce that every index adheres to a generic interface? I want to define code like this:

interface HasState<T> {
  state : T;
}

interface ModuleDefinition {
  [moduleName : string] : <T>HasState<T>;
}

Let's consider a scenario:

I have a TypeScript mapped type for an object:

interface ModuleDefinition {
  [moduleName : string] : HasState;
}

interface HasState {
  state : any;
}

This setup ensures that each value in a ModuleDefinition object includes a property named state.

However, when trying to extract the state objects using the mapped type:

type UnwrappedModules<Mod extends ModuleDefinition> = {
  [K in keyof Mod] : Mod[K]["state"];
}

and defining a function like this:

function unwrap<Mod extends ModuleDefinition>(mod: Mod) : UnwrappedModules<Mod> {
  // ...
}

const result = unwrap({
  apple: {
    state: {
      color: 'red'
    }
  }
}).apple

The variable result is of type any instead of { color : string }, which is unexpected. Is there a way to correctly infer the type in such cases in TypeScript?

Answer №1

Achieving the desired outcome is possible! The key lies in approaching the problem from a different angle: by utilizing mapped types, you can wrap data even if you are unable to unwrap it:

type StateWrapped<T> = {
  [K in keyof T]: HasState<T[K]>;
}

Rather than having your unwrap() function take plain type T as input and return an unwrapped T, consider having it accept the wrapped T and output the plain T:

function unwrap<T>(mod: StateWrapped<T>): T {
  var ret = {} as T;
  for (var k in mod) {
    ret[k] = mod[k].state;
  }
  return ret;
}

This approach passes type-checks and delivers the intended result:

const result = unwrap({
  apple: {
    state: {
      color: 'red'
    }
  },
  banana: {
    state: {
      ripe: false
    }
  }
});
console.log(result.apple.color.charAt(0));
console.log(result.banana.ripe);

Hopefully this explanation proves beneficial!

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

React/Typescript - Managing various event types within a unified onChange function

In my current project, I am working with a form that includes various types of input fields using the mui library. My goal is to gather the values from these diverse input components and store them in a single state within a Grandparent component. While I ...

How to extract key-value pairs from an object in a TypeScript API request

I am trying to extract the data '"Cursed Body": "100.000%"' from this API response using TypeScript in order to display it on an HTML page. Can anyone help me figure out how to do this? API Response { "tier": &q ...

I am looking to replicate a DOM element using Angular 4

I am interested in creating a clone of a DOM element. For example, if I have the following structure: <div> <p></p> <p></p> <p></p> <p></p> <button (click)="copy()"></button> & ...

Is it possible to use export default Enum in TypeScript?

I am facing an issue with exporting an enum object as default at the top level in my code. Here is what I tried: export default enum Hashes{ FOO = 'foo', BAR = 'bar', } However, this resulted in an error message: Module parse failed ...

What is the reason for the function to return 'undefined' when the variable already holds the accurate result?

I have created a function that aims to calculate the digital root of a given number. Despite my efforts, I am encountering an issue where this function consistently returns undefined, even though the variable does hold the correct result. Can you help me ...

Executing a single insert statement in a TypeScript Express application using PostgreSQL is taking over 240 milliseconds to complete

Seeking assistance with optimizing a db insert operation test using mocha for a node.js express app that utilizes fp-ts and pg npm package. The tests run successfully, but the insert test is taking over 240 ms to complete. The database table has a maximum ...

Incorporating Moralis into Ionic Angular with TypeScript

I'm currently developing an app using Ionic Angular (TypeScript) that will be compatible with both Android and iOS devices. I've decided to incorporate the Moralis SDK to establish a connection with the Metamask wallet. Here's a summary of ...

The array is not being spliced in the DOM, however, it is being spliced in the console - Ionic 2+/Angular

My scenario involves a dynamic array filled with items and values. The goal is to remove an item from the view list when a user clicks a button on that particular item. I'm struggling to identify why this functionality isn't working as expected. ...

Having trouble with TypeScript Library/SDK after installing my custom package, as the types are not being recognized

I have created my own typescript library using the typescript-starter tool found at . Here is how I structured the types folder: https://i.stack.imgur.com/igAuj.png After installing this package, I attempted a function or service call as depicted in the ...

Combining Axios with repeated promises

I am facing an issue with a loop in my GET request on the axis, and I cannot figure out why. const [ state, setState ] = useState<any[]>([]); ids.forEach((id) => { getData(id) .then((smth: Map<string, any>[]) => getNeededData ...

The conversion to ObjectId was unsuccessful for the user ID

I'm looking to develop a feature where every time a user creates a new thread post, it will be linked to the User model by adding the newly created thread's ID to the threads array of the user. However, I'm running into an issue when trying ...

When attempting to inject a provider from the same module, the dependencies cannot be resolved

Bug Report Current Issue Encountering an error when trying to instantiate the PaymentProcessorModule: Error: Nest cannot resolve dependencies of the PaymentProcessor (?, PaymentsService, ProcessingService). Please ensure that the TransactionsService argum ...

What causes TypeScript to flag spread arguments within callback wrappers?

My aim is to enhance a callback function in TypeScript by wrapping it with additional logic. In order to achieve this, I have an interface called Callbacks that outlines various callback signatures. The objective is to create a wrapper function that can lo ...

Tips for creating a default route with parameters in Angular Component Router?

I am trying to set a default route in my sub-component (using useAsDefault: true) and have parameters automatically passed to it, but I can't seem to find any information on how to accomplish this in the documentation. I have a parent component with t ...

Creating interactive forms - Incorporating dynamic checkbox options within the expansion panel element

Recently, I developed a basic movie list app with a checkbox list for genre filtering. Initially, I managed to achieve the desired functionality without using reactive forms. However, I am now exploring implementing the same functionality using reactive ...

Issue with login form in IONIC: Form only functions after page is refreshed

Encountering an issue with my Ionic login form where the submit button gets disabled due to invalid form even when it's not, or sometimes displays a console error stating form is invalid along with null inputs. This problem seems to have surfaced afte ...

Unable to assign dynamic key to Vue 3 directive for object property

Currently, I am utilizing the maska npm package to mask input fields in Vuetify. Within my setup, I have an array of masks that I make use of: export const Masks = { hour: { mask: "##:##", eager: true }, host: { mask: "#00.#00.#00.# ...

What is the correct way to invoke a method from a generic in TypeScript?

My goal is to develop a function that invokes a specific method on a generic object. Here's my initial attempt: type MethodName<S extends Object, M extends keyof S> = S[M] extends Function ? M : never const callMethod = <S, M extends keyof ...

Exporting Axios.create in Typescript can be accomplished by following a few simple

My code was initially working fine: export default axios.create({ baseURL: 'sample', headers: { 'Content-Type': 'application/json', }, transformRequest: [ (data) => { return JSON.stringify(data); } ...

Exploring the world of Typescript TSX with the power of generic

Introducing JSX syntax support in Typescript opens up new possibilities. I have an expression that functions smoothly with traditional *.ts files, but encounters issues with *.tsx files: const f = <T1>(arg1: T1) => <T2>(arg2: T2) => { ...