TypeScript object that necessitates a dynamic property type

I'm facing a specific scenario where I need to create an object with a property that can have one of two distinct types (custom classes).

export class MyType1 {
   // properties here
}

export class MyType2 {
   // properties here
}

class CustomType = MyType1 | MyType2

class Config {
   propOne: boolean;
   dynamicParam: { [key: string ]: CustomType }
}

The Config object is utilized in the following manner:

let config: Config = {
   propertyOne: true,
   dynamicParam: {
      key1: {
         // properties from type one
      },
      key2: {
         // properties from type two
      }
   }
}

If I explicitly specify the type when defining the object, like so:

   key1: <MyType1> {

   }

I receive intellisense for properties of MyType1 class. However, if I omit the type specification, I get intellisense for properties of both MyType1 and MyType2 classes (due to dynamicParam's type being CustomType [union type]).

So my question is, can I enforce required type definition when defining the object? Meaning that if I try to define a property as:

   key1: {

   }

I would receive an error message stating that a type must be specified?

Answer №1

Let's modify your code to ensure it compiles correctly and possesses particular attributes for better clarity:

interface MyType1 {
  a: string,
  b: number,
  c: boolean
}

interface MyType2 {
  d: string,
  e: number,
  f: boolean
}

type CustomType = MyType1 | MyType2

If you feel uneasy about the fact that the following code complies without any warnings:

let customType: CustomType = {
  a: "yes",
  b: 2,
  c: false,
  d: {what: "the"} // no error here
}

In TypeScript, although types are not "exact types", object literals undergo excess property checking when assigned to variables. This means unexpected properties will trigger a warning. For instance, this would result in an error:

let warnCustomType: CustomType = {
  a: "yes",
  b: 2,
  c: false,
  nope: { what: "the" } // error
  // Object literal may only specify known properties, 
  // and 'nope' does not exist in type 'CustomType'.
}

However, excess property checking doesn't work independently on each part of a union type like CustomType; instead, it checks the entire union as a whole. Hence, the property 'd' above won't raise a warning since it belongs to one part of the union. This is currently viewed as a bug in TypeScript, with potential resolution in future releases. Yet, enforcing developer-asserted property types remains unfeasible.

An alternative approach involves creating explicit exact types for unions, restricting properties accordingly. This can be achieved using conditional types like so:

type AllPossibleKeysOf<U> = U extends any ? keyof U : never;

type Exclusify<U> = [U] extends [infer V] ? V extends any ? (
  V & Partial<Record<Exclude<AllPossibleKeysOf<U>, keyof V>, never>>
) : never : never

Applying Exclusify to CustomType results in ExclusiveCustomType, limiting properties to specific parts of the union:

type ExclusiveCustomType = Exclusify<CustomType>;

This enforces stricter property constraints compared to CustomType. Exploring different approaches may yield varied outcomes. Best of luck!

Answer №2

When working with TypeScript's structural type system, an object literal that includes all the properties of either MyType1 or MyType2 will be valid. It seems there is no straightforward way to enforce a type assertion like <MyType1>, but if a discriminant property is added in the type definition, it becomes mandatory in the configuration. Here's an example:

export enum MyTypes { 
    TYPE1, TYPE2
}

export class MyType1 {
    kind: MyTypes.TYPE1;
   // some properties
}

export class MyType2 {
    kind: MyTypes.TYPE2;
   // some properties
}

type CustomType = MyType1 | MyType2;

class Config {
   propertyOne: boolean;
   dynamicParam: { [key: string ]: CustomType }
}

let config: Config = {
    propertyOne: true,
    dynamicParam: {
        key1: {
            kind: MyTypes.TYPE2,
            // properties from one type
        },
        key2: {
            kind: MyTypes.TYPE1,
            // properties from another type
        }
    }
};

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

Determining a value that increases to yield a fresh sum

I'm currently developing a character generator that determines your score based on the experience points you allocate to it. The scoring system is such that 1 XP gives you a score of 1, 3 XP gives you a score of 2, 6 XP gives you a score of 3, 10 XP g ...

Is there a way for TS-Node to utilize type definitions from the `vite-env.d.ts` file?

I am utilizing Mocha/Chai with TS-Node to conduct unit tests on a React/TypeScript application developed with Vite. While most tests are running smoothly, I am encountering difficulties with tests that require access to type definitions from vite-env.d.ts ...

Setting default selections for mat-select component in Angular 6

I've been attempting to preselect multiple options in a mat-select element, but I haven't been successful so far. Below is the snippet of HTML code: <mat-dialog-content [formGroup]="form"> <mat-form-field> <mat-select pla ...

Every time a module is imported, it is reloaded without utilizing the cache

Every time I attempt to import a module in my NextJS custom server app, like import { registerStartOfGuildTriviaListener } from '@/lib/trivia-manager';, the module is reloaded instead of using the previously loaded version. This poses an issue fo ...

The error message states that the property '$refs' cannot be found on the void type

Attempting to automatically focus on the next input field after typing 1 character. Encountering error: The property '$refs' does not exist on type 'void'. Here is the code snippet: setup() { const autoFocusNextInput = (event, max: ...

Retrieve the key values from an object of a generic type

Is there a way to retrieve the keys of the object when it is of type T? I attempted to accomplish this using different methods such as: function getGenericTypeKeys<T>(): string[] { return Object.keys({} as T); } and function getGenericTypeKeys< ...

Next.js does not support tooltips with custom children components

I created a unique Tooltip component and I'm attempting to include NextLink as the children. However, I encountered an error similar to the one below. Warning: Failed prop type: Invalid prop `children` supplied to `ForwardRef(Tooltip)`. Expected an e ...

Svelte user interface fails to update correctly after editing a writable array type

Currently, I am working on developing a crew creator tool for a rowing club to provide some context. The Writable container that holds the array is defined as follows import { writable, Writable } from 'svelte/store'; import type { CrewMember } ...

Issue with Variable Passing Correctly to Form

Can someone help me figure out how to update the value of textbox retentionx with '0OTHXXGK1DCA19JUN-thank you'? Currently, the text in the textbox is only displaying '0OTHXXGK1DCA19JUN-thank' and isn't fully visible. Code snippet ...

Use Angular to trigger a method when the Enter key is pressed, passing the current input text as a parameter

I need to extract the text from an input field and pass it to a method. Here is the code: index.component.html <input type="text" (keyup.enter)="sendData()" /> index.component.ts sendData() { console.log(The text from the input field); } Can ...

"Exploring the world of Ionic 2: uncovering its public variables

I'm facing an issue with Ionic 2, specifically with Angular. My concern revolves around a variable called "isConnected". I am unable to access it from an internal function within a function as it gives me an error saying "can't define property of ...

Pause until the user selects either the confirm or deny option before proceeding with the next action

As a beginner in Angular, I am seeking help with my code structure. I have three files: WarningComponent (which displays a warning modal using Bootstrap), modalService (responsible for opening modals), and votingComponent. Within the votingComponent, ther ...

How to display ngx-toastr using the show() method in Angular 2+ with a specific type

Working with ToastrService.success/error/warning/info() poses no issues for me, However, when attempting to utilize ToastrService.show(), I am unsure of the correct string type to pass along I made an attempt by sending an enum like so: export enum To ...

Troubles encountered while attempting to properly mock a module in Jest

I've been experimenting with mocking a module, specifically S3 from aws-sdk. The approach that seemed to work for me was as follows: jest.mock('aws-sdk', () => { return { S3: () => ({ putObject: jest.fn() }) }; }); ...

Retrieving data for a route resolver involves sending HTTP requests, where the outcome of the second request is contingent upon the response from the first request

In my routing module, I have a resolver implemented like this: { path: 'path1', component: FirstComponent, resolve: { allOrders: DataResolver } } Within the resolve function of DataResolver, the following logic exists: re ...

NextJS project utilizing the power of Typescript and composite structure

I recently revamped my NodeJS/Typescript project using workspaces and project references. Within one of the workspaces, I have a NextJS application with a recommended tsconfig.json configuration property: "noEmit": true However, this setting con ...

Discovering a solution to extract a value from an Array of objects without explicitly referencing the key has proven to be quite challenging, as my extensive online research has failed to yield any similar or closely related problems

So I had this specific constant value const uniqueObjArr = [ { asdfgfjhjkl:"example 123" }, { qwertyuiop:"example 456" }, { zxcvbnmqwerty:"example 678" }, ] I aim to retrieve the ...

Retrieve values of properties from an array

How can I retrieve property values from an array? const d = [{id: 'Cat'}, {id: 'Dog'}] type ids = ??? //place code here, type should be 'Cat' | 'Dog' (It would also be acceptable if it creates a const enum) ...

Understanding the explanation of the function declaration

declare abstract class CustomBootstrapConsole<X extends AppContext, Y extends Options = DefaultOptions> { protected customService: CustomConsoleService; protected customContainer: X; protected readonly customOptions: Y; constructor(op ...

Securing important code sections in Node/Express using Typescript: A guide

I'm fairly new to the world of JavaScript/Typescript/Node/Express, and as I've been researching, it seems there isn't a universally accepted method for locking critical sections of code in a Node/Express application. I've come across a ...