TypeScript interface with conditional optional parameters

Is it possible to achieve the following structure in TypeScript :

interface IMyInterface{
    firstname:string  // this means firstname is mandatory

    name:string  // this also means name is mandatory
}

How can we make either firstname or name, but not both, optional by adding a ? based on the presence of the other ?

If that's not feasible, what alternate approaches can we consider?

UPDATE: This question isn't the same as Typescript Interface - Possible to make "one or the other" properties required?.

We prefer avoiding creating separate interfaces for each optional element to avoid complexity in maintenance, naming issues and refactoring concerns.

Answer №1

When faced with the need to select just one key out of a set, consider using this generic approach called "EachOfTmp":

type EachOfTmp<T> = {// to make OneOf less gross
  [K in Keys<T>]: {
    _: {[X in K]: T[K]};
  }
};

// require only one of the keys
export type OneOf<T> = EachOfTmp<T>[Keys<T>]["_"] & Partial<T>;

const thing1: OneOf<{ a: number; b: number }> = { a: 2 } // valid
const thing2: OneOf<{ a: number; b: number }> = { b: 2 } // valid
const thing3: OneOf<{ a: number; b: number }> = {} // invalid

EDIT: I accidentally left out my handy Keys function -

export type Keys<T> = keyof T;
export function Keys<T>(o: T) {
  if (!o) {
    return [];
  }
  return Object.keys(o) as Keys<T>[];
}

Answer №2

Here's an alternative approach:

    interface IFullName {
        name:string;
        firstName: string;
    }

    let person: IFullName;
    // person = {}; // Error
    // person = { name: "" }; // Error, missing firstname
    // person = { firstname: "" }; // Error, missing name
    person = { name: "", firstname: "" }; // Ok

Answer №3

If there are additional members in the `IMyInterface` that need to be preserved, I suggest a more generalized approach based on the @Catalyst response.

type EachExpanded<T> = {
    [key in keyof T]: { [subKey in key]: T[key]; }
};

type FixedSubset<T, U> = Pick<T, Exclude<keyof T, U>>;

type AtLeastSubset<T, U> = Pick<T, Extract<keyof T, U>>;

type AtLeaseOne<T, U> = FixedSubset<T, U> & EachExpanded<AtLeastSubset<T, U>>[keyof AtLeastSubset<T, U>];

const example1: AtLeaseOne<{ a: number; b: number; c: string; }, 'a' | 'b'> =
    { a: 3, b: 4, c: '4' } // valid
const example2: AtLeaseOne<{ a: number; b: number; c: string; }, 'a' | 'b'> =
    { a: 1, c: '1' } // valid
const example3: AtLeaseOne<{ a: number; b: number; c: string; }, 'a' | 'b'> =
    { b: 2, c: '2' } // valid
const example4: AtLeaseOne<{ a: number; b: number; c: string; }, 'a' | 'b'> =
    { c: '3' } // invalid

Note that this solution utilizes the recently introduced `Exclude` and `Extract` keywords in TypeScript version 2.8.

In this code snippet, we assume type `T` represents the original type or interface, while `U` signifies the set of keys where at least one must be present.

The implementation involves creating a type that excludes properties requiring implementation from the original type `T`. This is achieved through `Pick<T, Exclude<keyof T, U>>`, which includes everything except items in `U`.

Additionally, another type is constructed containing elements where at least one must be present: `Pick<T, Extract<keyof T, U>>`.

The `EachExpanded` type defines the structure for each special set under the same key. For instance, if keys `'a'` and `'b'` need to become conditionally optional, `EachExpanded` creates:

{
    a: { a: number; };
    b: { b: number; };
}

This composite type is then combined using an intersection operator to enforce the presence of at least one item.

Ultimately, for the given example, the resulting type would look like this:

{ c: string; } & ({ a: number; } | { b: number; })

Answer №4

This is a sample of how it can be written.

interface PersonName{
    first_name?: string;
    last_name: string;
}

interface PersonFirstName{
    first_name: string
    last_name?: string
}

type Person = PersonName | PersonFirstName;

When implementing the Person interface, either last name or first name must be provided.

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

Issue with ng2-charts not rendering properly on the client side when utilized in Angular version 2.0.0-beta-17

Struggling with using ng2-charts in my Angular 2 app and encountering some challenges. app.ts import {Component} from 'angular2/core'; import {CHART_DIRECTIVES} from 'ng2-charts/ng2-charts'; @Component({ selector: & ...

Tips for preventing circular dependencies when using combineSlices in Redux-toolkit

When utilizing combineSlices with createAsyncThunk condition, I find it challenging to avoid circular dependency. My store initiation thunk looks like this: thunk.ts export const initiateFx = createAsyncThunk< InitiatePayload, string, { state: R ...

Error encountered in Vue 3 typescript: Uncaught TypeError - this.$on function is not defined in this context

Just set up a fresh installation of Vue 3 using vue-cli and typescript. Everything seems to be running smoothly, but as soon as I incorporate the https://vue-select.org/ package, I encounter this error in the browser console: Uncaught (in promise) TypeErro ...

The attributes for react-table pagination are not found in the 'TableInstance{}' type

Trying to set up a pagination feature using react-table, tutorials usually start with the following code snippet: const { getTableProps, getTableBodyProps, headerGroups, page, prepareRow, visibleColumns, ...

The expandable column headers in Primeng are mysteriously missing

I'm facing an issue with my expandable row in Angular2 using Primeng2, where the column headers for the expandable columns are not displaying. Below is the code snippet of my table with expandable rows: <p-dataTable [value]="activetrucks" expanda ...

"Utilize Typescript to dynamically check data types during runtime and receive alerts for any potential

INTRODUCTION I am currently working with Angular 6. In Angular, typescript is utilized to allow you to specify the data type of your function's arguments: public fun1(aaa: number) { console.log(aaa); } If I attempt to call fun1 with a parameter ...

Choose the appropriate data type for the class variable (for example, fArr = Uint32Array)

const functionArray: Function = Uint32Array; new fArr(5); The code snippet above is functioning properly. However, TypeScript is throwing a TS2351 error: "This expression is not constructable. Type 'Function' has no construct signatures". I wo ...

Using TypeScript, you can utilize RxJS to generate a fresh Observable named "Array" from a static array

I've successfully created an observable from an array, but the issue is that its type shows as Observable<number> instead of Observable<number[]> getUsers(ids: string[]): Observable<number[]> { const arraySource = Observable.from ...

Typescript requires that the argument passed is undefined

Typescript: 2.4.1 I am exploring the creation of a helper function to produce redux action creators. Here is what I have: interface IAction<T extends string, P = undefined> { type: T; payload: P; } function createAction<T extends strin ...

What is the best way to use a generic callback function as a specific argument?

TS Playground of the problem function callStringFunction(callback: (s: string) => void) { callback("unknown string inputted by user"); } function callNumberFunction(callback: (n: number) => void) { callback(4); // unknown number inputt ...

Methods to acquire the 'this' type in TypeScript

class A { method : this = () => this; } My goal is for this to represent the current class when used as a return type, specifically a subclass of A. Therefore, the method should only return values of the same type as the class (not limited to just ...

Having trouble getting my Angular project up and running - facing issues with dependency tree resolution (ERESOLVE)

Currently, I am in the process of following an Angular tutorial and I wanted to run a project created by the instructor. To achieve this, I referred to the steps outlined in the 'how-to-use' file: How to use Begin by running "npm install" within ...

Determine the amount of time that can be allocated based on the attributes contained within the object

I am faced with a JSON structure like the one below: var meetings = [ { id: '1', start_time: "2020-11-15T08:30:00+00:00", end_time: "2020-11-15T14:15:00+00:00" }, { id: '2', start_time: &quo ...

What is the best way to generate a unique UUID for every object created within a loop?

I am working on a typescript method that eliminates hashtags from a string and saves them in an array within a model. Each element in the hashtag array is assigned a unique UUID along with the complete string added to the model. However, I am facing an iss ...

How to create an array of objects using an object

I have a specific object structure: { name: 'ABC', age: 12, timing: '2021-12-30T11:12:34.033Z' } My goal is to create an array of objects for each key in the above object, formatted like this: [ { fieldName: 'nam ...

Preventing the compilation process from overwriting process.env variables in webpack: A step-by-step guide

Scenario I am currently working on developing AWS Lambda functions and compiling the code using webpack. After reading various articles, I have come to know that the process.env variables get automatically replaced during compilation. While this feature ...

Filter an array of objects in Typescript by using another array of strings

I'm having trouble with my TypeScript filter function. Here is an array of objects: [ { "id": 12345, "title": "Some title", "complexity": [ { "slug": "1", // This is my search term "name": "easy" }, { ...

Is there a distinction in Typescript between the return types of Object.seal and .freeze?

I am looking to safeguard the constant object content from any changes during runtime. This includes alterations to both the object structure and its content. The preferred method for achieving this is by using Object.freeze. interface iRO<T> { r ...

What's the best way to address this blind spot?

Exploring the world of TypeScript has left me puzzled by a scenario where TypeScript does not perform type checking as expected. I'm running into an issue where 'this.a.method()' appears to be error-free when it should actually throw an erro ...

Encountered an issue while trying to encapsulate the App using Redux

Upon attempting to integrate Redux with my TypeScript React application, I encountered this error message: Error: Objects are not valid as a React child (found: object with keys {children}). If you meant to render a collection of children, use an array i ...