Uncover the value type of a key in TypeScript without using a discriminated union

I want to implement a type map that ensures the type of an object's value for a specific key is determined by the value of another key. For example:

type CurrencyValue = {
  code: string;
  value: number;
};

type FieldMap = {
  text: string;
  currency: CurrencyValue;
};

type FieldKind = keyof FieldMap;

type Field<T extends FieldType> = {
  kind: FieldKind;
  value: FieldMap[T];
};

In this scenario, if I create an object with the structure Field, and its kind is set to "text", then the expected type of value should be a string. If the kind is "currency", then the value should match the CurrencyValue type.

While I can almost achieve this functionality with the existing setup, I find myself needing to specify a parameter in the type:

// this works
const field: Field<"currency"> = {
  type: "currency",
  value: {
    code: "USD",
    value: 12,
  },
};

// this doesn't
// error: Generic type 'Field' requires 1 type argument(s).ts(2314)
const field: Field = {
  type: "currency",
  value: {
    code: "USD",
    value: 12,
  },
};

However, I wish to avoid having to explicitly define the type parameter in these cases. Is there a way for TypeScript to infer the type of value without explicit declaration?

One approach that has worked for me involves using discriminated unions:

type CurrencyField = {
    type: 'currency',
    value: CurrencyValue;
}

type TextField = {
    type: 'text',
    value: string;
}

type Field = CurrencyField | TextField;

const field: Field = {
    type: 'text',
    value: 'string'
}

However, creating multiple new types whenever a new version of a Field is introduced is not ideal. Therefore, it would be more convenient to simply update the mapping.

Thank you for your assistance!

Answer №1

To create the discriminated union, you don't have to manually write it out. You can achieve this with the following code:

type Field<T extends FieldKind = FieldKind> = T extends T ? {
  kind: T;
  value: FieldMap[T];
}: never;

Playground Link

This code leverages the distributive property of conditional types to generate a union of object types from a union of field names (such as

"currency"|"text"
), creating one object type for each constituent of the original union (
{ kind: "currency"; value: FieldMap["currency"]; } | { kind: "text"; value: FieldMap["text"]; }

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-file-viewer shrinks any document to a compact size

I have been searching extensively for information on how to adjust file sizing in react-file-viewer without any success. My objective is to utilize the react-file-viewer to allow users to click on a filename hyperlink and open the file (be it an image, do ...

Is it possible to assign default values to optional properties in JavaScript?

Here is an example to consider: interface Parameters { label: string; quantity?: number; } const defaultSettings = { label: 'Example', quantity: 10, }; function setup({ label, quantity }: Parameters = { ...defaultSettings }) { ...

How to effectively handle null in Typescript when accessing types with index signatures unsafely

Why am I getting an error that test might be potentially undefined even though I've enabled strictNullCheck in my tsconfig.json file? (I'm unsure of the keys beforehand) const a: Record<string, {value: string}> = {} a["test"].va ...

Error in Angular 13: Struggling to remove the likelihood of an object being null

I am working on a piece of code that includes the following: var item = document.getElementById("div0"); item.parentNode.removeChild(item); // The error seems to be here Every time I run this code, I encounter the error message: object is p ...

Tips for resolving conflicts between sequelize and angular within a lerna monorepo using typescript

Managing a monorepo with Lerna can be quite challenging, especially when working with both Node.js and Angular in the same project. In my setup, Angular is using "typescript": "~3.5.3". For Node.js to work seamlessly with Sequelize, I have the following ...

Is it possible to efficiently structure intricate JSON data onto interfaces in TypeScript with the incorporation of observables?

I am currently working on creating a simple web news reader that relies heavily on the Google News API (). I have set up all the necessary services, models, etc. However, I am having difficulty mapping the data onto specific interfaces. The Google API retu ...

There seems to be an issue with the authorization function in nextauthjs utilizing TypeScript

In my NextJS application utilizing nextAuth with TypeScript, I am encountering difficulties implementing the credentials provider. Below is a snippet from my api\auth\[...nextauth]\route.ts file: CredentialsProvider({ name: 'cre ...

I've been working on setting up a navbar in React/typescript that links to various routes, but I've hit a snag - every time I try to create a link

import React from 'react' import { Link } from 'react-router-dom' export default function NavBar() { return ( <div className='NavContainer'> <link to='/home'>Home</link> <l ...

I have successfully implemented useLazyQuery in a functional component, but now I am looking to integrate it into a class component. Can you provide guidance on how to achieve

Recently, I encountered an issue with my functional component that contains 3 checkboxes and 1 button. I utilized the useLazyQuery hook to ensure that my query was only sent upon clicking the button. However, a major drawback is that my component re-rend ...

How can I select just one element to be impacted by a click event when using map in TypeScript?

Currently, I'm facing an issue where I want to change the icon of a button when it's selected. The problem is that using map affects all buttons even if only one is selected. // ... const [clicked, setClicked] = useState(false); <Button sta ...

Encountered an issue while attempting to integrate Nebular into my Angular application

As a newcomer to Angular, I decided to try installing Nebular using the command ng add @nebular/theme. However, I encountered an error in the process. Upon entering the command into my terminal, the following error message appeared: ? Which Nebular theme ...

Whenever signing in with Next Auth, the response consistently exhibits the values of "ok" being false and "status" being 302, even

I am currently using Next Auth with credentials to handle sign-ins. Below is the React sign-in function, which can be found at this link. signIn('credentials', { redirect: false, email: email, password: password, ...

Next.js routes handlers do not have defined methods parameters

Struggling to find the cause of undefined params Currently delving into the world of Nextjs api routes, I keep encountering an issue where my params are coming up as undefined when trying to use them in the HTTP method. My setup includes prisma as my ORM ...

Display or Conceal Multiple Divisions Using Angular 2

I am looking to implement a functionality using Li lists to toggle the visibility of multiple divs in Angular 2. Initially, all divs on the page will be visible. When viewing on a smaller screen, I want to hide some divs and show a specific div when a cor ...

TS2367: Given any input, this condition will consistently evaluate to 'false'

I'm struggling to understand why the compiler is not including null as a possible type for arg, or perhaps I am misinterpreting the error message. static vetStringNullable(arg:any): string|null { if (typeof arg === 'string') { ...

Is there a more efficient method for invoking `emit` in Vue's Composition API from an external file?

Is there a more efficient way to access the emit function in a separate logic file? This is my current approach that is functioning well: foo.js export default (emit) => { const foo = () => { emit('bar') }; return { foo }; } When ...

Angular: the xhrRequest is failing to be sent

I am facing an issue with a service function that handles an HTTP post request. The request does not get sent for some reason. However, when I add a subscription to the post method, the request is successfully executed. In other services that have the sam ...

When using the `const { }` syntax, which attribute is made accessible to the external

I am using the ngrx store as a reference by following this example: https://stackblitz.com/edit/angular-multiple-entities-in-same-state?file=src%2Fapp%2Fstate%2Freducers%2Fexample.reducer.ts Within the code in example.reducer.ts, there is this snippet: ...

Utilizing React with Typescript: A guide to working with Context

I have a super easy app snippet like import React, { createContext } from 'react'; import { render } from 'react-dom'; import './style.css'; interface IAppContext { name: string; age: number; country: string; } const A ...

Encountered Typescript issue when utilizing typed forms in Angular

Below is the interface I am working with: export interface ILoginDto { email: string; password: string; } Here is a snippet of the relevant code from the component: import { FormBuilder, FormGroup, Validators } from '@angular/forms'; export ...