Accurate TS declaration for combining fields into one mapping

I have a data structure called AccountDefinition which is structured like this:

something: {
  type: 'client',
  parameters: {
    foo: 3
  }
},
other: {
  type: 'user',
  parameters: {
    bar: 3
  }
},
...

The TypeScript declaration is functioning properly, but I am currently facing difficulties while trying to create a "generator" function (doThings) and struggling with how to correctly define its types. I am considering refactoring all these types as well.

export interface Spec {
  type: `${SpecType}`
  parameters: unknown
}

export interface UserSpec extends Spec {
  type: `${SpecType.USER}`
  parameters: UserSpecParameters
}

export interface ClientSpec extends Spec {
  type: `${SpecType.CLIENT}`
  parameters: ClientSpecParameters
}

export interface AccountDefinition {
  [k: string]: UserSpec | ClientSpec
}

export enum SpecType {
  USER = 'user',
  CLIENT = 'client'
}

export type SpecParametersMap = {
  user: {
    bar?: number
  }
  client: ClientSpecParameters
}

export interface UserSpecParameters {
  bar?: number
}

export interface ClientSpecParameters {
  foo: number
}

export const doThing = <T extends SpecType>( // Preferably not generic if inference works from type
  type: T,
  parameters: SpecParametersMap[T]
): void => {
  const account: AccountDefinition = {
    // Example
    foo: {
      type: 'client',
      parameters: {
        foo: 3
      }
    },
    // TS Error:
    // Type '{ parameters: SpecParametersMap[T]; type: T; }' is not assignable to type 'UserSpec | ClientSpec'.
    // Type '{ parameters: SpecParametersMap[T]; type: T; }' is not assignable to type 'ClientSpec'.
    // Types of property 'type' are incompatible.
    // Type 'T' is not assignable to type '"client"'.ts(2322)
    data: {
      parameters,
      type
    }
  }
}

doThing(SpecType.CLIENT, { foo: 4 })

Test it on the Playground here.

Answer №1

The issue at hand lies within the arguments. TypeScript does not treat them as a unified data structure.

To resolve this, you need to combine your arguments into a single data structure.

export enum SpecType {
  USER = 'user',
  CLIENT = 'client'
}

export interface Spec {
  type: `${SpecType}`
  parameters: unknown
}

export interface ClientSpecParameters {
  foo: number
}

export interface UserSpecParameters {
  bar?: number
}

export interface UserSpec extends Spec {
  type: `${SpecType.USER}`
  parameters: UserSpecParameters
}

export interface ClientSpec extends Spec {
  type: `${SpecType.CLIENT}`
  parameters: ClientSpecParameters
}

type AllowedValues = UserSpec | ClientSpec;

export interface AccountDefinition {
  [k: string]: AllowedValues
}


export const doThing = (data: AllowedValues): void => {
  const account: AccountDefinition = {
    foo: {
      type: 'client',
      parameters: {
        foo: 3
      }
    },
    data
  }
}

doThing({ type: SpecType.CLIENT, parameters: { foo: 4 } }) // okay
doThing({ type: SpecType.USER, parameters: { bar: 42 } }) // okay


You can explore this alternative approach, although note that there is no elegant destructuring involved

Why is it not possible to utilize the generic param to access both arguments?

In reality, you can achieve this using multiple methods. Here's one example:

export const doThing = <T extends SpecType>(data: T extends SpecType.CLIENT ? ClientSpec : UserSpec): void => {
    const account: AccountDefinition = {
        foo: {
            type: 'client',
            parameters: {
                foo: 3
            }
        },
        data
    }
}

doThing({ type: SpecType.CLIENT, parameters: { foo: 4 } }) // okay
doThing({ type: SpecType.USER, parameters: { bar: 42 } }) // okay

doThing({ type: SpecType.USER, parameters: { foo: 4 } }) // expected error
doThing({ type: SpecType.CLIENT, parameters: { bar: 42 } }) // expected error

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

Rxjs: Making recursive HTTP requests with a condition-based approach

To obtain a list of records, I use the following command to retrieve a set number of records. For example, in the code snippet below, it fetches 100 records by passing the pageIndex value and increasing it with each request to get the next 100 records: thi ...

Node OOM Error in Webpack Dev Server due to Material UI Typescript Integration

Currently in the process of upgrading from material-ui v0.19.1 to v1.0.0-beta.20. Initially, everything seems fine as Webpack dev server compiles successfully upon boot. However, upon making the first change, Node throws an Out of Memory error with the fol ...

Error TS7053 occurs when an element is given an 'any' type because a 'string' expression is being used to index an 'Object' type

When attempting to post data directly using templateDrivenForm and retrieve data from Firebase, I encountered the following type error message. Here are the relevant parts of my code: // Posting data directly using submitButton from templateDrivenForm onC ...

The MatInput value will only display after the page is reloaded or refreshed

After refreshing the page, a matInput field displays a value or result that was previously hidden. https://i.stack.imgur.com/q9LQI.png By selecting or highlighting within the matInput, the value or result becomes visible. https://i.stack.imgur.com/SqaLA.p ...

The reason for the Jest failure is that it was unable to locate the text of the button

As someone who is new to writing tests, I am attempting to verify that the menu opens up when clicked. The options within the menu consist of buttons labeled "Edit" and "Delete". However, the test fails with the message: "Unable to find an element with te ...

Defining the type of the createAction() function in TypeScript within the context of Redux Toolkit

Lately, I have been delving into the redux-toolkit library but I am struggling with understanding the type declaration of the createAction function as demonstrated below. The createAction function returns a PayloadActionCreator which includes a generic of ...

Unable to execute function on Child Element

I am encountering an issue when trying to call a function on a child component. Whenever I try to invoke it by clicking, I always receive an error indicating that the function is undefined. Here is the code in my Parent component: import {MatTableCompone ...

Exploring the File Selection Dialog in Node.js with TypeScript

Is it possible to display a file dialog in a Node.js TypeScript project without involving a browser or HTML? In my setup, I run the project through CMD and would like to show a box similar to this image: https://i.stack.imgur.com/nJt3h.png Any suggestio ...

Discovering a way to retrieve objects from an array of objects with matching IDs

Here is a code snippet I put together to illustrate my objective. arr = [ { id:1 , name:'a', title: 'qmummbw' }, { id:2 , name:'b', title: 'sdmus' }, { id:2 , name:'', title: 'dvfv' }, ...

Vue3: The module './assets/logo.png' and its corresponding type declarations are not found

The project was created using the following command: vue create testtype3 Link to image: https://i.sstatic.net/vMuq0.png App.vue: <template> <img alt="Vue logo" src="./assets/logo.png"> <img :src="MyIcon" ...

What could be the reason for the component not receiving data from the service?

After attempting to send data from one component to another using a service, I followed the guidance provided in this answer. Unfortunately, the data is not being received by the receiver component. I also explored the solution suggested in this question. ...

The implementation of Symbol.species in the Node.js Buffer class to generate a RapidBuffer seems illogical and confusing

While exploring the source code of ws, a popular WebSocket implementation for Node.js, I stumbled upon this specific piece of code: const FastBuffer = Buffer[Symbol.species]; But what exactly is this FastBuffer used for? Surprisingly, it seems that they a ...

Ensuring Function Parameter Usage in Typescript and Angular 5

Currently, I am developing a component using Angular 5 and Ionic 4. My objective is to include a Refresher event to hide the refresh spinner whenever the user triggers the final function to conceal the spinner. Provided below is an excerpt of my code: e ...

Require assistance in accurately assigning a date to a Date[] in Typescript Array without altering current elements

In my current code, I have a loop that verifies if a date is a holiday and then adds it to an array. The issue I'm facing is that whenever I assign a new element to the array, all previous data in the list gets updated to the latest element. Here&apos ...

Node_modules seem to be missing

After completing the TypeScript 101 QuickStart tutorial using Visual Studio 2015 and Node Tools for Visual Studio, I attempted to import the 'winston' npm module. However, no matter what path I specify, Visual Studio indicates that it cannot loca ...

Tips on incorporating esbuild extensions in the template.yaml file of AWS SAM

Currently, my TypeScript Lambda functions are managed using the AWS Serverless Application Model (SAM), and I rely on esbuild for the build process. I'm interested in incorporating esbuild plugins into my build process to enable support for TypeScrip ...

Utilizing React Router with the power of useCallback

My route configuration is set up as follows: const defineRoutes = (): React.ReactElement => ( <Switch> <Redirect exact from="/" to="/estimates" /> <Route exact path="/estimates" component={CostingPa ...

Snackbar and RTK Query update trigger the error message: "Warning: Cannot update during an existing state transition."

I've built a basic ToDos application that communicates with a NodeJS backend using RTK Query to fetch data, update state, and store cache. Everything is functioning properly as expected with the communication between the frontend and backend. Recently ...

Issue with Angular2/4 Module: Unable to locate 'three' in the application directory

As a newcomer to Angular and TypeScript, I am encountering difficulties when trying to import the @types/three npm package. I have created a new Angular project and attempted to use the three package, but I keep receiving the following error: Module not f ...

The Event Typing System

I am currently in the process of setting up a typed event system and have encountered an issue that I need help with: enum Event { ItemCreated = "item-created", UserUpdated = "user-updated", } export interface Events { [Event.Ite ...