What is the best way to define the type of an object in TypeScript when passing it to a function

I am currently working on a function that accepts an object of keys with values that have specific types. The type for one field is determined by the type of another field in the same object. Here is the code:

// Consider this Alpha type and echo function...

type NoInfer<T> = [T][T extends unknown ? 0 : never]

interface Alpha<Foo extends string> {
  foo: Foo
  bar: `Depends on ${NoInfer<Foo>}`
}

declare const echo: <T extends string>(x: Alpha<T>) => void

echo({ foo: 'beta', bar: 'Depends on beta'})

// @ts-expect-error Trailing 2 is wrong
echo({ foo: 'beta', bar: 'Depends on beta 2'})

// Now we need a function (bravo) that takes a keyed index of Alphas...

declare const bravo: <T extends { [k: string]: Alpha<string> }>(xs: T) => void

bravo({
  one:  { foo: `1`,  bar: `Depends on 1` },
  // @ts-expect-error 1 !== 1x           <-- fails
  oneX: { foo: `1x`, bar: `Depends on 1` },
  two:  { foo: `2`,  bar: `Depends on 2` },
  // @ts-expect-error 2 !== 2x           <-- fails
  twoX: { foo: `2x`, bar: `Depends on 2` },
})

// How can I make this work?

playground link

As indicated by the "fails" comments, while I can get Alpha to work initially, I encounter difficulties with more complex objects of Alphas. Can you assist me in resolving this issue? Thank you!

Answer №1

You have the option to structure this in a way where T serves as an object type with properties represented by the string values you provide as type arguments to Alpha. Then, you can turn xs into a mapped type over T, like so:

declare const bravo: <T extends { [K in keyof T]: string }>(
  xs: { [K in keyof T]: Alpha<T[K]> }
) => void

The recursive constraint { [K in keyof T]: string } is utilized to ensure that all properties of T are of type string, without relying on the index signature { [k: string]: string }, which would reject interface types lacking index signatures (refer to microsoft/TypeScript#15300 and How to constrain a TypeScript interface to have only string property values? for more details).

Since the type of xs is a homomorphic mapped type (as described in What does "homomorphic mapped type" mean?), the compiler can infer T from it when you invoke the function (although this was previously documented, the new handbook doesn't seem to mention it 🤷‍♂️). Let's put it to the test:

bravo({
  one: { foo: `1`, bar: `Depends on 1` },  // valid
  oneX: { foo: `1x`, bar: `Depends on 1` }, // error
  // --------------> ~~~
  // Type 'Depends on 1' is not compatible with type 'Depends on 1x'
  two: { foo: `2`, bar: `Depends on 2` }, // valid
  twoX: { foo: `2x`, bar: `Depends on 2` }, // error
  // --------------> ~~~
  // Type 'Depends on 2' is not compatible with type 'Depends on 2x'
})

All seems well. If you hover over the function call in an IDE with IntelliSense capabilities, you'll receive Quick Info

/* const bravo: <{
    one: "1";
    oneX: "1x";
    two: "2";
    twoX: "2x";
}>(xs: {
    one: Alpha<"1">;
    oneX: Alpha<"1x">;
    two: Alpha<"2">;
    twoX: Alpha<"2x">;
}) => void */

This shows that T is inferred as

{one: "1", oneX: "1x", two: "2", twoX: "2x"}
, leading to xs's type being compared against
{one: Alpha<"1">, oneX: Alpha<"1x">, two: Alpha<"2">, twoX: Alpha<"2x">}
, resulting in validation for the one and two properties but failing for the oneX and twoX properties, providing the desired errors.

Access the code via 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

Angular 10 does not fulfill promises as expected

In the Angular 10 project I'm working on, I encountered an issue while trying to call an API request asynchronously using promises. The code I wrote didn't seem to execute the API call as expected and kept exiting at the first line without progre ...

Leverage the Nuxeo client SDK with Angular 6 for seamless integration with RESTClient in

Looking to integrate the Nuxeo ClientSdk with my Angular 6 client to consume its REST API, but facing issues due to the lack of typescript definitions for this JavaScript package. Tried importing the library into my project using the following code snippe ...

What could be causing the error message "why can't shows read property

Within my Ionic-Angular application, I have successfully loaded the content from the database and printed it on the console. However, I am facing an issue where I cannot bind this content to the view. The error message that appears is displayed in https:// ...

Angular 2 Mixup: Using Leaflet and Google Maps with Incorrect Tile Order

I am encountering an issue while working on Leaflet and Google within Angular 2. The problem lies in the Tilemill tiles not rendering properly as they are displaying in a strange order. Here is a screenshot of the current state: https://i.stack.imgur.com/ ...

Why isn't my Enum functioning properly to display the colored background?

Why isn't the Background Color showing up when I pass in the BGColor Prop dynamically in my next.js + Tailwind app? I have tried passing in the prop for my component, but the color is not appearing. <Title title='This is HOME' descripti ...

An error occurred due to attempting to access properties of null while trying to read 'useMemo' in a Next.js TypeScript application

Currently engaged in a Next.js 13 project with TypeScript, utilizing the useDrag hook. No errors are being flagged in my Visual Studio Code editor; however, upon attempting to render the page, an error message surfaces. The issue points to a problem with t ...

Guide on sending files and data simultaneously from Angular to .NET Core

I'm currently working on an Angular 9 application and I am trying to incorporate a file upload feature. The user needs to input title, description, and upload only one file in .zip format. Upon clicking Submit, I intend to send the form data along wit ...

Vue.js - A dynamic parent component generates content based on data passed from a renderless child component

I am currently working on developing a system for generating buttons using vue 3 and vue-class-component. The main goal is to create a flexible button generation process, where the number of buttons generated can vary (it could be just one or multiple). Us ...

Custom type declaration file in Typescript fails to function properly

I have searched through countless solutions to a similar issue, but none seem to work for me. I am attempting to utilize an npm package that lacks TypeScript type definitions, so I decided to create my own .d.ts file. However, every time I try, I encounter ...

What causes the error message "No exported member 'ɵɵFactoryDeclaration' in @angular/core/core" to appear during project compilation?

I am facing an issue where the global Angular CLI version is 13.0.1 and the local version in my project is 10.2.3. Despite making changes to some components (without touching package.json), I encountered an error during the build step of my bitbucket pipel ...

Guide on retrieving a nested JSON array to extract a comprehensive list of values from every parameter within every object

A JSON file with various data points is available: { "success": true, "dataPoints": [{ "count_id": 4, "avg_temperature": 2817, "startTime": "00:00:00", "endTime": "00:19:59.999" }, ... I am trying to extract all the values of & ...

What is the best method for storing a third-party image in cache?

Running my website, I aim to achieve top-notch performance scores using LightHouse. I have successfully cached all the images I created (Cache-Control: public, max-age=31536000). Unfortunately, third-party website images are not cached. How can I cache t ...

Enforcing Type Safety on String Enums in Typescript Using the 'const' Assertion

I am trying to use an enum for type checking purposes. Here is the enum I have: enum Options { Option1 = "xyz", Option2 = "abc" } My goal is to create a union type of 'xyz' | 'abc'. However, when I attempt to d ...

Typescript iterative declaration merging

My current project involves creating a redux-like library using TypeScript. Here is an example of the basic action structure: interface ActionBase { type: string; payload: any; } To customize actions for different types, I extend the base interface. ...

The category 'Moment' cannot be assigned to the category 'Date'. The characteristic 'toDateString' is not present in the category 'Moment'

I recently integrated moment into my Angular2 application, and encountered an issue when attempting to assign the date of this week's Saturday to a variable of type date, case "weekend": this.fromDate = moment().startOf('week ...

Typescript is failing to perform type checking

I'm encountering an issue while trying to utilize TypeScript type checking with the following code snippet: abstract class Mammal { abstract breed(other: Mammal); } class Dog extends Mammal { breed(other: Dog) {} } class Cat extends Mammal { ...

acquire tabulations from every single document within the templates on DocuSign

When using Docusign, it is possible to retrieve tabs data for a specific document within a template by specifying the documentId. However, I have not been able to locate a method to obtain tabs data for all documents contained within a template. ...

Issue with Angular UI Bootstrap accordion heading behavior when used in conjunction with a checkbox is causing

I have implemented a checkbox in the header of an accordion control using Bootstrap. However, I am facing an issue where the model only updates the first time the checkbox is clicked. Below is the HTML code for the accordion: <accordion ng-repeat="tim ...

Utilizing a forwardRef component in TypeScript to handle child elements

Working with @types/react version 16.8.2 and TypeScript version 3.3.1. This forward refs example was taken directly from the React documentation with added type parameters: const FancyButton = React.forwardRef<HTMLButtonElement>((props, ref) => ...

Implement handleTextChange into React Native Elements custom search bar component

I need help with passing the handleTextChange function in the SearchBarCustom component. When I try to remove onChangeText={setValue} and add onchange={handleTextChange}, I am unable to type anything in the search bar. How can I successfully pass in the ...