Choose the property category

Is there a more efficient way to specify the type of a property in TypeScript without resorting to casting? Take a look at this example:

interface Overlay {
    type: "modal" | "drawer"
    other?: number
}

const rec = {
    obj1: { type: "modal" },
    obj2: { type: "drawer" },
    ...
}

The object rec currently has the type

{ obj1: { type: string }, obj2: { type: string } }
. The desired type is
{ obj1: Overlay, obj2: Overlay, ... }
.

I have attempted different methods:

  1. Option 1 - effective but verbose and creates additional variables
const obj1: Overlay = { type: "modal" }
const obj2: Overlay = { type: "drawer" }
const ...
const rec = { obj1, obj2, ... }
  1. Option 2 - prone to hiding errors (e.g. {} as Overlay is accepted by TS). Using satisfies only validates on a per-property basis.
const rec = {
    obj1: { type: "modal" } as Overlay,
    obj2: { type: "drawer" } as Overlay,
    ...
}
  1. Option 3 - loses key type information (
    "obj1" | "obj2" | ...
    replaces with string)
const rec: Record<string, Overlay> = {
    obj1: { type: "modal" },
    obj2: { type: "drawer" },
    ...
}
  1. Option 4 - overlooks details about the 'other' property and improperly sets the type of 'type' to either "modal" or "drawer" instead of the union type
const rec = {
    obj1: { type: "modal" },
    obj2: { type: "drawer" },
    ...
} as const

Are there better alternatives than #1 to achieve the correct type?

Answer №1

Using Record Type in TypeScript

A useful way to structure your data in TypeScript is by utilizing the Record type.

type AcceptedKey = 'obj1' | 'obj2' | 'obj3'

interface Overlay {
  type: 'modal' | 'drawer'
  other?: number
}

const rec: Record<AcceptedKey, Overlay> = {
  obj1: { type: 'modal' },
  obj2: { type: 'drawer' },
  obj3: { type: 'drawer' },
}

By defining a Record with specific keys and their corresponding data types, you can ensure type safety when working with objects. Each key from AcceptedKey must be present in the Record.

The syntax for the Record type is outlined below:

 /**
 * Construct a type with a set of properties K of type T
 */
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

Utilizing Partial Record for Optional Keys

If you want some keys to be optional, you can combine Partial with Record like so:

const partialRec: Partial<Record<AcceptedKey, Overlay>> = {
  obj1: { type: 'modal' },
  obj2: { type: 'drawer' },
}

This approach works well for scenarios where you have a known set of keys with varying degrees of presence.

Flexible Key Formatting with Record

To allow for dynamic key names based on a format rather than fixed values, you can incorporate numeric types into string literals:

type AcceptedKeyFormat = `obj${number}`

interface Overlay {
  type: 'modal' | 'drawer'
  other?: number
}

const rec2: Record<AcceptedKeyFormat, Overlay> = {
  obj0: { type: 'modal' },
  obj1: { type: 'drawer' },
  obj999: { type: 'drawer' },
}

This method provides flexibility in defining keys and allows for varying sets of keys within each record.

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

I'm curious about the significance of this in Angular. Can you clarify what type of data this is referring

Can anyone explain the meaning of this specific type declaration? type newtype = (state: EntityState<IEntities>) => IEntities[]; ...

What could be causing my TypeScript code to not be recognized as CommonJS?

I rely on a dependency that is transpiled to ES6. My goal is to leverage ES2019 features in my own code. Ultimately, I aim to output ES6. This is how I set up my tsconfig { "compilerOptions": { "module": "CommonJS" ...

Cypress automation script fails to trigger Knockout computed subscription

Within my setup, I have implemented two textboxes and a span to display the result. Date: <input data-bind="value: dateValue"/> Number: <input data-bind="value: dateValue"/> Result : <span data-bind="text: calculatedValue">Result Should ...

Having trouble resolving the signature of a class decorator when invoked as an expression with @Injectable in Angular

Error Message: Unable to resolve the signature of a class decorator when called as an expression. The argument type 'ClassDecoratorContext' is not compatible with the parameter type 'string | symbol | undefined'. After creating a book ...

Tips for including an authorization token in an HTTP request

I encountered a 401 unauthorized error when trying to access my REST endpoint, likely due to the security measures I have implemented. I suspect that there might be an issue with how I am handling the HTTP headers. The application utilizes a Spring Boot b ...

Want to enhance user experience? Simply click on the chart in MUI X charts BarChart to retrieve data effortlessly!

I'm working with a data graph and looking for a way to retrieve the value of a specific column whenever I click on it, and then display that value on the console screen. Check out my Data Graph here I am using MUI X charts BarChart for this project. ...

Detecting if a string is in sentence or title case with a typeguard

When setting the sameSite property of a cookie, it must be either Strict, Lax, or None. However, the package I'm using uses lowercase values for this attribute. Therefore, I need to adjust the first letter of the string: let sentenceCaseSameSite: &quo ...

Issue - firestore has not been defined (Occurs strictly after the use of "then")

Having an issue: I successfully create fake users in my database, but encounter a problem when starting the 'for' loop. The error I'm facing is: Error adding document: TypeError: Cannot read property 'firestore' of undefined I ...

Displaying a React component within a StencilJS component and connecting the slot to props.children

Is there a way to embed an existing React component into a StencilJS component without the need for multiple wrapper elements and manual element manipulation? I have managed to make it work by using ReactDom.render inside the StencilJS componentDidRender ...

Error message: The ofType method from Angular Redux was not found

Recently, I came across an old tutorial on Redux-Firebase-Angular Authentication. In the tutorial, there is a confusing function that caught my attention: The code snippet in question involves importing Actions from @ngrx/effects and other dependencies to ...

Checking JavaScript files with TSLint

After spending many hours attempting to make this work, I still haven't had any success... I am wondering: How can I utilize TSLint for a .js file? The reason behind this is my effort to create the best possible IDE for developing numerous JavaScrip ...

Leveraging Shared Modules Component across multiple modules in Angular can enhance code re

In my project structure, I have a shared folder containing shared.module.ts. Additionally, there is a modules folder with sub-modules, one of which is Dashboard.module.ts. Inside the shared module, I created a custom sidebar menu that I intend to use withi ...

Type error TS2322: You can't assign type 'undefined' to type 'string'

I am currently in the process of creating a chatbot for an upcoming exhibition and encountered the following error: src/app/chat/chat.component.ts:32:9 - error TS2322: Type 'undefined' is not assignable to type 'string'. 32 th ...

The NullInjector has issued an error regarding the lack of a provider for the Decimal

I recently integrated lazy loading into my application. However, one of my services is in need of the DecimalPipe. The structure of my modules goes like this: service -> shared module -> App module To give you more context, I have already added "Co ...

Discovering a variable within an enzyme wrapper for the locate function

Struggling through testing with jest + enzyme. I have an array called OptionsArray that contains options mapped to buttons in a component. In my testing suite for the component, I attempted to do the following: import React from 'react'; import { ...

Is there a way to optimize Typescript compiler to avoid checking full classes and improve performance?

After experiencing slow Typescript compilation times, I decided to utilize generateTrace from https://github.com/microsoft/TypeScript/pull/40063 The trace revealed that a significant amount of time was spent comparing intricate classes with their subclass ...

Issue found: Passing a non-string value to the `ts.resolveTypeReferenceDirective` function

Encountering the following error: Module build failed (from ./node_modules/ts-loader/index.js): Error: Debug Failure. False expression: Non-string value passed to ts.resolveTypeReferenceDirective, likely by a wrapping package working with an outdated res ...

What could be causing input to be blocked in certain situations while using my Angular directive with compile function?

Recently, I created a directive that adds a class based on a certain condition. You can find the code snippet at the end of this question. The directive functions as expected in a simple use case where it's applied to a required field: <input typ ...

The problem with the onClick event not triggering in Angular buttons

My issue revolves around a button in my code that is supposed to trigger a function logging the user out. However, for some reason, the event associated with it doesn't seem to be functioning at all. Here's the relevant code snippet: TS File: imp ...

What is the best way to search for and isolate an array object within an array of objects using Javascript?

I am attempting to narrow down the list based on offerings const questions = [ { "id": 2616, "offerings": [{"code": "AA"},{"code": "AB"}]}, { "id": 1505, "offerings": [ ...