Ensure that the object contains distinct keys that correspond to an Enum and are assigned specific values for each key

In my application, I have implemented two modules (CAR, BUS) and I want all other related components to be linked with these using an enum. For instance, within a config file, I aim to store configurations for either of the mentioned modules, ensuring that they are always associated with the corresponding module key from the enum. Therefore, the config objects should utilize the module name from the enum as their key.

Here is an example that reflects my current scenario.

enum Vehicles {
  CAR = "car",
  BUS = "bus"
}

type VehicleKeys = keyof typeof Vehicles;

type VehicleValues = `${Vehicles}`;

interface CarConfig{
  engine: string;
};

interface BusConfig{
  color: string
}

/**
 * {
 *  car: CarConfig;
 *  bus: BusConfig;
 * }
 */

type VehicleConfig = { [K in VehicleValues]?: CarConfig | BusConfig }; 

interface Config extends VehicleConfig {
  name: string,
}

const config: Config = {
  name: "test",
  car: {
    engine: "My Car",
  },
  bus: {
    color: "Red",
  }
}

if(config.car){
  console.log( config.car.engine)
}

To ensure that the config keys are limited to the enum keys, I opted for [K in VehicleValues]. This prevents anyone from adding unrelated elements to the config unless they correspond with the enum. However, this creates a challenge when specifying which interface is relevant to each key.

/**
 * {
 *  car: CarConfig;
 *  bus: BusConfig;
 * }
 */

I could choose any of the interfaces, but then accessing values becomes problematic because the compiler cannot determine the type of data.

Property 'engine' does not exist on type 'CarConfig | BusConfig'.
  Property 'engine' does not exist on type 'BusConfig'.

Is there a way to enforce restrictions on the keys while also specifying which interface should correspond to its value?

Access the sandbox here

Answer №1

You have the ability to define types using computed property keys as long as those keys are of a literal or unique symbol type. Additionally, values from an enum such as Vehicles.CAR and Vehicles.BUS can be considered as literal types for this purpose. This makes it possible for you to construct the Config interface like so:

interface Config {
    name: string;
    [Vehicles.CAR]?: CarConfig,
    [Vehicles.BUS]?: BusConfig
}

Your code logic should align correctly with this structure:

const config: Config = {
    name: "test",
    car: {
        engine: "My Car"
    },
    bus: {
        color: "Red"
    }
};

if (config.car) {
    console.log(config.car.engine); // All good
}

Feel free to check out the actual implementation in the TypeScript playground here.

Answer №2

It seems that the issue stems from utilizing an interface that inherently permits authors to include extra properties. To constrain the allowable properties, it is advisable to opt for a type instead:


interface CarConfig {
    engine: string;
};

interface BusConfig {
    color: string
}

type Config = {
    name: string,
    car?: CarConfig,
    bus?: BusConfig,
}

const config: Config = {
    name: "test",
    car: {
        engine: "My Car",
    },
    bus: {
        color: "Red",
    },
    /*
        Type '{ name: string; car: { engine: string; }; bus: { color: string; }; plane: string; }' is not assignable to type 'Config'.
          Object literal may only specify known properties, and 'plane' does not exist in type 'Config'.ts(2322)
    */
    plane: {},
}

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

What is preventing me from accessing the properties of the parent abstract class?

Why am I encountering the error message "Abstract property 'field0' in class 'SuperAbstractClass' cannot be accessed in the constructor", even though upon inspecting the transpiled code, it appears that we do have access to th ...

The property express.json() is not recognized

Why doesn't Typescript recognize the express.json() function, even though many tutorials claim it should compile without errors? Could I have overlooked something in my code? An example tutorial that suggests this code works: https://auth0.com/blog/n ...

Discover the step-by-step guide to setting up forwarding in React Router 5

Just diving into the world of React and TypeScript. I'm working with a component called Err. Is there a way to redirect it using React Router 5? import React, { FC, Fragment, useEffect } from "react"; const Err: FC<{ error: string }> = ({ erro ...

A guide on displaying data from objects containing arrays in React using TypeScript

Hey there! I'm currently facing a challenge in rendering values within an object containing arrays. Let me show you an example of the object structure: data { info1: [{note: "this is note 1", status: true}], info2: [{note: "this i ...

Do we need to import Vue in every component when using Nuxt with TypeScript?

I recently integrated TypeScript into Nuxt using the guidelines provided in the documentation: However, I have a specific question regarding component setup. Should I always include import vue from "vue" and export default Vue.extend ({}); in al ...

Is the type safety of Typescript Discriminated Unions failing on nested objects?

I am working on a project using Typescript 4 where I am trying to create an object with discriminated unions. However, it seems that the type safety is not functioning as expected. export enum StageType { PULL = 'pull', FILTER = 'fil ...

Having trouble with the sx selector not functioning properly with the Material UI DateTimePicker component

I'm currently working with a DateTimePicker component and I want to customize the color of all the days in the calendar to match the theme color: <DateTimePicker sx={{ "input": { color: "primary.main&quo ...

How to utilize the Hide/Unhide data series feature in Chart.js with Angular 2

Currently utilizing PrimeNG charts that utilize chartJS as the foundation. I am exploring a scenario where I want to hide/show a specific data series using an external button. Usually, this is achieved by clicking on the label of the relevant data series ...

Exploring i18next language settings with Typescript

I wrote a function that retrieves locale information like this: fetchLocale.ts import i18next from 'i18next'; export const fetchLocale = (locale) => { return i18next.t('value', { locale }) } Additionally, here is the test I create ...

Encountering an error with the Angular 2 SimpleChanges Object during the initial npm start process

Within my Angular 2 application, there exists a component that holds an array of objects and passes the selected object to its immediate child component for displaying more detailed data. Utilizing the "SimpleChanges" functionality in the child component a ...

Utilizing TypeScript to Populate an observableArray in KnockoutJS

Is there a way to populate an observableArray in KnockoutJS using TypeScript? My ViewModel is defined as a class. In the absence of TypeScript, I would typically load the data using $.getJSON(); and then map it accordingly. function ViewModel() { var ...

Retrieving a distinct value from an Observable

I am currently attempting to extract the monthlyFee value from this specific response body. ...

Using Angular with Google Maps: Learn how to retrieve a list of markers from a map and implement onClick events for each one

I am currently using the AGM angular module for Angular 2+ to integrate the Google Map API. In my project, I have a directive that displays waypoints as markers on the map using the Google Map Directions Service. Now, I am looking for a way to handle the ...

Struggling to find a solution for directing to the featured homes page without the content overlapping with my navbar and search component. Any assistance would be greatly

Looking for assistance with routing to the featured homes page without the content overlapping my navbar and search component. I simply want it to direct to a new URL without importing the components unless specifically needed. Check out this link I suspe ...

The element 'flat' is not found within the specified type

My challenge involves utilizing the flat() method in a TypeScript script. In my tsconfig.json file, I have set the target to es2017 and defined an interface for the input variable. However, I keep encountering this error message: Property 'flat' ...

An Unexpected Typescript Error Occurred While Creating an RxCollection With RxDB

I'm new to RxDB and I've come across a strange Typescript error in my Electron project. Here are the relevant parts of my code: import RxDB, { RxCollection, RxDatabase } from "rxdb"; RxDB.plugin(require("pouchdb-adapter-idb") ...

Is your Angular form consistently coming back as invalid?

Within my Angular app, I have implemented a reactive form containing three checkboxes: The first checkbox allows users to choose whether they want to remain anonymous. If left unchecked, the name field will be visible. If checked, the name field will be ...

Managing multiple `ng-content` slots in Angular can be a daunting task. Here

I am facing an issue with a component where I have declared an input as follows: @Input() isOverlay: boolean The template html for this component looks like this: <ng-template *ngIf="isOverlay" cdkConnectedOverlay [cdkConnected ...

What causes an error during the compilation of an Angular package containing a singleton class?

I am currently in the process of creating an Angular library. Within this library, I have developed a singleton class to manage the same SignalR connection. Here is the code implementation: import * as signalR from '@microsoft/signalr'; export c ...

The deno bundle operation failed due to the absence of the 'getIterator' property on the type 'ReadableStream<R>'

When attempting to run deno with bundle, an error is encountered: error: TS2339 [ERROR]: Property 'getIterator' does not exist on type 'ReadableStream<R>'. return res.readable.getIterator(); ~~~~~~~~~~~ ...