Using TypeScript to create an interface with keys that must come from an enum, where at least one key is optional

I am in need of an interface for an object that must contain either a userId or a customerId, but not both.

The key should be determined by an enum (ensuring consistent naming).

I have adapted the following method from a previous example:

export enum EId {
  userId = 'userId',
  customerId = 'customerId',
}

export type IIdParam = {
  [key in EId]: string;
};

export interface IIdParamString extends IIdParam {
  paramString: string;
}

Ultimately, I aim to create this object:

const params: IIdParamString = {
    userId: String('1234'),
    paramString: 'limit',
};

However, I encounter an error: If [key in EId] is not optional, it requires both userId and customerId, and if optional, neither ...

Perhaps this approach is not suitable for my requirements. It seems that the value in the enum may not be necessary for the key, but it is challenging to abstract it further from the provided example.

Answer №1

Take a look at this scenario:


export enum EId {
    userId = 'userId',
    customerId = 'customerId',
}

type EnsureOne<Obj, Keys = keyof Obj> = Keys extends keyof Obj ? Record<Keys, string> : never

// credits to https://stackoverflow.com/questions/65805600/type-union-not-checking-for-excess-properties#answer-65805753
type UnionProps<T> = T extends T ? keyof T : never;
type StrictUnionHelper<T, TAll> =
    T extends any
    ? T & Partial<Record<Exclude<UnionProps<TAll>, keyof T>, never>> : never;

type StrictUnite<T> = StrictUnionHelper<T, T>

export type IIdentifierParamString =
    & StrictUnion<EnsureOne<typeof EId>>
    & {
        paramString: string;
    }

/**
 * Yep
 */
const data: IIdentifierParamString = {
    userId: '1234',
    paramString: 'limit',
};

/**
 * Anticipate errors
 */
const data2: IIdentifierParamString = {
    paramString: 'limit',
};
const data3: IIdentifierParamString = {
    userId: '1234',
    customerId: 'sdf',
    paramString: 'limit',
};

EnsureOne - detailed explanation can be found here

StrictUnite - you can find in-depth explanation here. This function operates similarly to using the concept of `never` as you have done.

Is my methodology generally suitable for solving my issue, ensuring it is either this or that?

Absolutely, your method works. However, I suggest considering discriminated unions (also known as tagged unions). For example, refer to F# discriminated unions. Each union has its own tag/marker which aids the compiler in distinguishing them.

Could this approach be implemented using an interface instead? Regrettably - no. It must be a type since an `interface` can solely extend statically known types. Refer to the example below:

// An interface can only extend an object type or intersection of object types with statically known members.
export interface IIdentifierParamString extends StrictUnion<EnsureOne<typeof EId>> { // error
    paramString: string;
}

In relation to your final query, I may need clarification. Feel free to revise your question or consider asking separately.

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 the method for defining a monkey patched class in a TypeScript declarations file?

Let's say there is a class called X: class X { constructor(); performAction(): void; } Now, we have another class named Y where we want to include an object of class X as a property: class Y { xProperty: X; } How do we go about defining ...

Is it possible to define multiple regular expressions for a single field using yup in a form validation?

My goal is to validate an input to only accept URLs in Google Maps format. There are three possible valid outputs for a Google Maps URL: (for Google Maps app) (I only want to focus on ) (when sharing directly from Google Maps) Currently, I am able to ...

Angular 11 throws an error stating that the argument of type 'null' cannot be assigned to a parameter of type 'HttpClient'

As I embark on my journey to becoming a developer, I encounter a problem when passing a null argument as shown below: todos.component.spec.ts import { TodosComponent } from './todos.component'; import { TodoService } from './todo.servic ...

Adding files to an Angular ViewModel using DropzoneJS

I am facing a challenge in extracting file content and inserting it into a specific FileViewModel. This is necessary because I need to bundle all files with MainViewModel which contains a list of FileViewModel before sending it from the client (angular) to ...

Error in TypeScript logEvent for Firebase Analytics

Currently utilizing firebase SDK version 8.0.2 and attempting to record a 'screen_view' event, encountering an error message stating: Error: Argument of type '"screen_view"' is not assignable to parameter of type '" ...

Is there a way to check for keys in a TypeScript object that I am not familiar with?

I have a good understanding of the unknown type in TypeScript. I am dealing with untrusted input that is typed as unknown, and my goal is to verify if it contains a truthy value under the key input.things.0. function checkGreatness(input: unknown) { retu ...

Unable to fetch data from Firebase in Angular 7

I have been trying to fetch a data set from firebase database and populate my drop-down list in Angular 7. However, despite conducting extensive research, I am still unable to resolve the issue. Here is the error message that I encounter: ERROR Error: "Ca ...

Backend communication functions seamlessly within the service scope, yet encounters obstacles beyond the service boundaries

I'm facing an issue with accessing data from my backend. Although the service successfully retrieves and logs the data, when I try to use that service in a different module, it either shows "undefined" or "Observable". Does anyone have any suggestions ...

Issue encountered when importing a font in TypeScript due to an error in the link tag's crossorigin

How do I troubleshoot a TypeScript error when importing a custom font, such as a Google font? <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> Below is the specific error message: Type 'boolean' is ...

Issues encountered when trying to upload images to Firestore Storage

I am attempting to upload an image and store its URL in a Firestore document. To achieve this, I have the following code snippet: This function uses the device camera to capture the photo. selectImage(): Promise<any> { return new Promise(resolv ...

Issue with Angular 2 page refresh is not triggering blur event

In our Angular 2 component, updates (HTTP PUT) are triggered when a user enters data and either tabs to the next field or hits enter. The event handling for this is set up using both blur and key.enter. Blur works efficiently as it is fired when the user ...

Issue with displaying data using a custom pure pipe and boolean in ngIf condition

For my current web project, I require a friendship/follow feature. There are two roles involved: admins and regular users. Regular users have the ability to follow other users, while admins do not possess this capability. When a user wishes to follow anot ...

A guide on incorporating a set of components to be utilized as custom HTML elements in Angular

I am in the process of revamping my application to be more modular on the UI side, with a focus on separating different elements including: App header Left navigation panel Main content on the right side of the nav panel I have successfully figured out ...

Maintaining the original function signature upon returning it

I am attempting to develop an interceptor function, specifically a throttle function. Let's look at this example: function throttle(func: Function, wait: number = 0): Function { let previous: { args: any[]; timestamp: number; ...

Organize and eliminate unnecessary imports in tsx and ts files using Visual Studio

Despite the trend among my colleagues to switch to Visual Studio Code for front end development, I stand by my preference for using Visual Studio as my single IDE. One feature that I really miss is Visual Studio Code Organize imports. How can I remove unu ...

Execute function once the service call has finished

Having a service function that needs to be called private callService() { this.functionOne(); this.functionTwo(); this.functionThree(); } private getOtherInfo() { // pure sync here this.getId(this.user['data']); this.getType(t ...

Change array association with object extension

I am looking to transform the assignment below into one that utilizes object spread: bData.steps[bStepKey].params= cData[cKey] My attempt so far has been unsuccessful. bData= {...bData, steps:{ ...bData.steps[bStepKey], params: cData[cKey]}} ...

Replace deprecated TypedUseSelectorHook with createSelectorHook for improved functionality

In my TypeScript code, I used the following to create a typed selector with Redux: import { useSelector, TypedUseSelectorHook } from 'react-redux'; export interface RootState { user: { account: string; } }; export const useTyped ...

Utilize Promise.race() in Playwright Typescript to anticipate the appearance of one of two locators

Recently, I've delved into the world of Typescript while exploring Playwright for Test Automation. There's a scenario where two elements - Validated and Failed - can appear after some loading. In Selenium, my go-to method is to use WebDriverWait ...

What is the best way to ensure smooth collaboration between OnChange and OnKeyDown?

I'm currently working on a weather app that integrates with the OpenWeather API and am facing a challenge with the search input functionality. My goal is to capture user input after they press the enter key. To achieve this, I'm using the OnChang ...