TypeScript mandates the inclusion of either one parameter or the other, without the possibility of having neither

Consider the following type:

export interface Opts {
  paths?: string | Array<string>,
  path?: string | Array<string>
}

The requirement is that the user must provide either 'paths' or 'path', but it is not mandatory to pass both. Currently, the issue is that this code compiles without errors:

export const foo = (o: Opts) => {};
foo({});

Is there a way in TypeScript to allow for two or more optional parameters where at least one is required?

Answer №1

If needed, you can utilize the following syntax:

export type Choices = { choice: string | Array<string> } | { options: string | Array<string> }

For better clarity, consider rephrasing as follows:

type StringOrArr = string | Array<string>;

type ChoiceOpts  = { choice : StringOrArr };
type OptionsOpts = { options: StringOrArr };

export type Choices = ChoiceOpts | OptionsOpts;

Answer №2

If you have an existing interface defined and want to prevent duplicate declarations, one approach is to create a conditional type that takes a type and returns a union with each type containing a single field (along with a record of never values for any additional fields to disallow specifying extra fields).

export interface Options {
    paths?: string | Array<string>,
    path?: string | Array<string>
}

type SingleField<T, Key extends keyof T = keyof T> =
    Key extends keyof T ? { [Prop in Key]-?:T[Key] } & Partial<Record<Exclude<keyof T, Key>, never>>: never
export const handleOptions = (o: SingleField<Options>) => {};
handleOptions({ path : '' });
handleOptions({ paths: '' });
handleOptions({ path : '', paths:'' }); // error
handleOptions({}) // error

Additional Information:

A closer look at the type manipulation technique employed here. We utilize the distributive property of conditional types to essentially iterate over all keys of the T type. The distributive property requires an extra type parameter to function, so we introduce Key for this purpose with a default value of all keys since we aim to extract all keys from type T.

Our goal is to derive individual mapped types for each key of the original type, resulting in a union of these mapped types, each encapsulating just one key. This process removes optionality from the property (indicated by -?, detailed here) while maintaining the same data type as the corresponding property in T (T[Key]).

The final aspect worth elaborating on is

Partial<Record<Exclude<keyof T, Key>, never>>
. Due to how object literals undergo excess property checks, it's possible to assign any field of the union to an object key within an object literal. For instance, a union like
{ path: string | Array<string> } | { paths: string | Array<string> }
permits employing the object literal
{ path: "", paths: ""}</code, which isn't ideal. To remedy this issue, we mandate that if other properties of <code>T
(beyond Key, denoted by Exclude<keyof T, Key>) are included in the object literal of any union member, they must be of type never (hence using
Record<Exclude<keyof T, Key>, never>>
). By partially modifying the preceding record, explicit specification of never for every member is avoided.

Answer №3

This is a functional example.

It allows for the use of a generic type T, specifically with a value of string.

The generic type OneOrMore can be either a single instance of T or an array containing multiple instances of T.

Your input object type Opts is structured to have either a property path containing OneOrMore<T>, or a property paths also containing OneOrMore<T>. There is no other acceptable option provided in the definition.

type OneOrMore<T> = T | T[];

export type Opts<T> = { path: OneOrMore<T> } | { paths: OneOrMore<T> } | never;

export const exampleFunction = (o: Opts<string>) => {};

exampleFunction({});

An issue arises with the usage of {} as an argument.

Answer №4

export type Opts={paths: string | Array<string>,  path?: undefined}  |
                 {paths?: undefined,  path: string | Array<string>}

In my opinion, this code is quite clear and easy to comprehend.

Answer №5

Searching for a unique union type that suits your needs?

A proposal was presented in the past, but unfortunately, it was rejected. You can read more here.

The suggested solutions didn't resonate with me as I prefer straightforward types over complex ones.

Have you considered using function overloading? It worked for me in a similar scenario.

interface Option1 {
  paths: string | string[];
}

interface Option2 {
  path: string | string[];
}

function foo(o: Option1): void;
function foo(o: Option2): void;
function foo(o: any): any {}

foo({ path: './' });
foo({ paths: '../' });
// The following two lines result in an error: No overload matches this call.
foo({ paths: './', path: '../' });
foo({})

If you prefer using arrow functions, you can adjust the code like this:

interface Option1 {
  paths: string | string[];
}

interface Option2 {
  path: string | string[];
}

interface fooOverload {
  (o: Option1): void;
  (o: Option2): void;
}

const foo: fooOverload = (o: any) => {};

foo({ path: '2' });
foo({ paths: '2' });
// The following two lines will show an error: No overload matches this call.
foo({ paths: '', path: ''});
foo({});

I hope this solution works for you!

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

Does nestjs support typescript version 3.x?

Currently embarking on a new project using Nestjs. I noticed in one of its sample projects, the version of Typescript being used is 2.8. However, the latest version of Typescript is now 3.2. Can anyone confirm if Nest.js supports version 3.x of Typescrip ...

In React, the Textarea component that displays the character count only updates its value after a page reload

Contained within this element is the following component: import React, { useEffect, useState } from 'react'; import { TextareaTestIds } from '../utils/test-ids'; export interface TexareaProps extends React.TextareaHTMLAttributes<H ...

I am encountering issues with running my tests using react-testing-library alongside TypeScript

Currently facing issues with react-testing-library in my TypeScript-based React project. Despite researching and following various tutorials, I am unable to resolve the problem. I have experimented with changing configurations in babel.config.js, tsconfig ...

"Frustrating issue with Firebase-admin dependency farmhash-modern resulting in webassembly error

Facing an issue while setting up firebase-admin SDK on my nextjs + TS project. Every time I try to call a SDK function, I encounter a webAssembly error. Specifically, when trying to configure a middleware for the server-side API and calling the verifyIdTok ...

Utilizing the WebSocket readyState to showcase the connection status on the application header

I am currently in the process of developing a chat widget with svelte. I aim to indicate whether the websocket is connected or not by utilizing the websocket.readyState property, which has the following values: 0- Connecting, 1- Open, 2- Closing, 3- Close ...

After executing a query to a PostgreSQL database, I encountered an error preventing me from using res.send(). The error message stated: "Cannot set headers after they are sent to the client."

It may sound strange, but it's a true story. I was busy building an API on node.js when I encountered a peculiar error. As soon as the first res.status().send() was triggered during query processing, VS Code threw a "Cannot set headers after they are ...

Adjust the specific data type to match its relevant category

Is there a method to alter literal types in TypeScript, for instance. type T1 = ""; type T2 = 1 I am interested in obtaining string for T1 and number for T2. Regarding collections, I am unsure, but I assume it would involve applying it to the generic typ ...

How Angular can fetch data from a JSON file stored in an S3

I am attempting to retrieve data from a JSON file stored in an S3 bucket with public access. My goal is to parse this data and display it in an HTML table. http.get<Post>('https://jsonfile/file.json').subscribe (res => { cons ...

How can we verify if a React component successfully renders another custom component that we've created?

Consider this scenario: File ComponentA.tsx: const ComponentA = () => { return ( <> <ComponentB/> </> ) } In ComponentA.test.tsx: describe("ComponentA", () => { it("Verifies Compo ...

Running an HTTP request conditionally within combineLatest

I'm working on a combineLatest function that makes 2 http requests, but I only want the second request to be made if a specific condition is satisfied. combineLatest([ this.leadsService.fetchALLLeadsActivityChart(this.clientId, this.getParams(option ...

Parsing error encountered while trying to handle an unexpected token at line 214, character 33. It appears that an appropriate loader is missing to process this particular file type

I've been developing a Typescript React project for the past few months without any issues. However, things took a turn yesterday when I decided to run npm audit fix and npm audit fix --force in order to address some security concerns that appeared ou ...

Issues with accessing view variables within a directive query are persisting

I am struggling with a specific directive: @Directive({ selector: '[myDirective]' }) export class MyDirective implements AfterViewInit { @ViewChild('wrapper') wrapper; @ViewChild('list') list; ngAfterViewInit() { ...

The error message "localStorage is undefined in Angular Universal" indicates that the local

I have chosen to utilize universal-starter as the foundation of my project. Upon initialization, my application reads a token containing user information from localStorage. @Injectable() export class UserService { foo() {} bar() {} loadCurrentUse ...

Arranging Pipe Methods in RxJS/Observable for Optimal Functionality

In the class Some, there is a method called run that returns an Observable and contains a pipe within itself. Another pipe is used when executing the run method. import { of } from 'rxjs'; import { map, tap, delay } from 'rxjs/operators&ap ...

NextJS VSCode Typescript results in breakpoints becoming unbound

I have been following the instructions provided by Next.js from their official documentation on debugging using Visual Studio Code found here: https://nextjs.org/docs/advanced-features/debugging#using-the-debugger-in-visual-studio-code When attempting to ...

The dropdown cannot be disabled because it is being passed as an input parameter

We are currently utilizing PrimeNG along with Angular 15. Scenarios: According to the requirements, we need the ability to disable the PrimeNG dropdown control based on a selection. Problem: The disabled property of <p.dropdown> is not functioning ...

Adding additional properties to Material UI shadows in Typescript is a simple process that can enhance the visual

https://i.stack.imgur.com/9aI0F.pngI'm currently attempting to modify the Material UI types for shadows, but encountering the following error when implementing it in my code. There is no element at index 25 in the tuple type Shadows of length 25. I&a ...

The cookie value is blank once after moving to the second page

I'm facing an issue with 2 domains where performing an action on the first domain results in setting a cookie that should be accessible on both domains. However, when trying to read the value of this cookie on the second domain, it appears empty. Can ...

The error message "Property <property> is not recognized on the type 'jQueryStatic<HTMLElement>'" is indicating an issue with accessing a specific property within the TypeScript codebase that utilizes Angular CLI, NPM,

My Development Environment I am utilizing Angular, Angular CLI, NPM, and Typescript in my web application development. Within one of my components, I require the use of jQuery to initialize a jQuery plugin. In this particular case, the plugin in question ...

What is the reason for VS Code not displaying doc comments from type properties when suggesting descriptions and hover info for TypeScript objects?

The problem is clearly visible in the image below, as it pertains to the popup box in VSCode displaying type information and comments. Although the code works fine in VSCode, TypeScript Playground fails to display the comments properly, so it's best ...