251

I'm trying to create an interface that could have

export interface MenuItem {
  title: string;
  component?: any;
  click?: any;
  icon: string;
}
  1. Is there a way to require component or click to be set
  2. Is there a way to require that both properties can't be set?
Nix
  • 57,072
  • 29
  • 149
  • 198
  • 1
    Possible duplicate of [Typescript Interface - Possible to make "one or the other" properties required?](https://stackoverflow.com/questions/37688318/typescript-interface-possible-to-make-one-or-the-other-properties-required) – Shepmaster Aug 17 '19 at 15:47

11 Answers11

338

With the help of the Exclude type which was added in TypeScript 2.8, a generalizable way to require at least one of a set of properties is provided is:

type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
    Pick<T, Exclude<keyof T, Keys>> 
    & {
        [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>
    }[Keys]

And a partial but not absolute way to require that one and only one is provided is:

type RequireOnlyOne<T, Keys extends keyof T = keyof T> =
    Pick<T, Exclude<keyof T, Keys>>
    & {
        [K in Keys]-?:
            Required<Pick<T, K>>
            & Partial<Record<Exclude<Keys, K>, undefined>>
    }[Keys]

Here is a TypeScript playground link showing both in action.

The caveat with RequireOnlyOne is that TypeScript doesn't always know at compile time every property that will exist at runtime. So obviously RequireOnlyOne can't do anything to prevent extra properties it doesn't know about. I provided an example of how RequireOnlyOne can miss things at the end of the playground link.

A quick overview of how it works using the following example:

interface MenuItem {
  title: string;
  component?: number;
  click?: number;
  icon: string;
}

type ClickOrComponent = RequireAtLeastOne<MenuItem, 'click' | 'component'>
  1. Pick<T, Exclude<keyof T, Keys>> from RequireAtLeastOne becomes { title: string, icon: string}, which are the unchanged properties of the keys not included in 'click' | 'component'

  2. { [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>> }[Keys] from RequireAtLeastOne becomes

    { 
        component: Required<{ component?: number }> & { click?: number }, 
        click: Required<{ click?: number }> & { component?: number } 
    }[Keys]
    

    Which becomes

    {
        component: { component: number, click?: number },
        click: { click: number, component?: number }
    }['component' | 'click']
    

    Which finally becomes

    {component: number, click?: number} | {click: number, component?: number}
    
  3. The intersection of steps 1 and 2 above

    { title: string, icon: string} 
    & 
    ({component: number, click?: number} | {click: number, component?: number})
    

    simplifies to

    { title: string, icon: string, component: number, click?: number} 
    | { title: string, icon: string, click: number, component?: number}
    
KPD
  • 5,281
  • 4
  • 20
  • 19
  • 14
    Thank you very much for descriptive examples. Really informative. – Eduard Jul 24 '18 at 18:47
  • 3
    Thanks for an extremely informative and well formed response. I've been playing with this example so I can better understand it. It appears that if you give `component` and `click` a type other then `any` an object with both at least one set of valid properties will pass. I assume that is because of the way the type reduces to `{ title: string, icon: string, component: any} | { title: string, icon: string, click: any}` Which states the type is 3 properties instead of 4 with one being optional. I'm trying to find documentation on the use of the array in the map notation but can't. – Sam R Jan 28 '19 at 19:51
  • 1
    This. is. pure. genius. You should definitely submit them here https://github.com/sindresorhus/type-fest – maxime1992 Mar 27 '19 at 10:14
  • @SamR That is a really good point. I updated the answer to add `& Partial>>` to `RequireAtLeastOne`. I also updated the playground link so `component` and `click` are `number` types instead of `any` to show an example of it now correctly declaring an object invalid in the case you brought up. Thanks! – KPD May 07 '19 at 01:47
  • 9
    Can anyone explain what the hyphen (minus sign) in the expression `[K in keys]-` does? – Ross Brasseaux Oct 16 '19 at 01:30
  • 3
    @Lopsided It removes any optional modifiers that might have existed for `K` in the original object `T`. See https://stackoverflow.com/questions/49655419/mapped-types-removing-optional-modifier As for its purpose in `[K in Keys]-?:` specifically: I just did some testing and it looks like it actually doesn't make a difference in the final result, but I put it in just to be certain that `RequireAtLeastOne` behaves the same way regardless of whether the properties specified for `Keys` were originally optional or not. – KPD Oct 21 '19 at 17:49
  • A little more complex scenarios, how would it be to `RequireOnlyOneOrNoneOfThem`? And also how to request a combination? Example: `type OnlyOne = RequireOnlyOne` Or click, or component, or label with options – Eduardo Dallmann May 23 '20 at 16:42
  • 2
    Note: This is part of the official Microsoft Azure Typescript SDK https://learn.microsoft.com/en-us/javascript/api/@azure/keyvault-certificates/requireatleastone?view=azure-node-latest – Simon_Weaver Jul 24 '20 at 18:28
  • 10
    Since TS 3.5 we can make the "at least one of" even shorter by using the `Omit` helper (a shortcut for `Pick` + `Exclude`): `type RequireAtLeastOne = Omit & { [ P in R ] : Required> & Partial> }[R];`. The base frame is the same, so only warrants a comment (quick note: in `[K in keys]-?:`, the `-?` modifier can be safely omitted: optionality is preserved in both versions) – Oleg Valter is with Ukraine Jan 15 '21 at 20:42
  • This is a cute solution to the OP but is not generalizable. In my case, I want properties (A and B) XOR (property C), and it's not at all clear how to use this solution for my very similar problem. I vastly prefer to turn the problem interface into a [discriminated union](https://basarat.gitbook.io/typescript/type-system/discriminated-unions), as it makes for much more readable code than using advanced language features like this. – Jthorpe Jan 06 '22 at 20:37
  • @OlegValteriswithUkraine i think one more updated answer for RequireOnlyOne is: ```export type RequireOnlyOne = Omit & { [K in keyof Required]: Required> & Partial, undefined>>; }[Keys]; ``` We can change the -?: operator to set the Type from the second generic type as required – Cobi Dec 15 '22 at 10:08
  • @RossBrasseaux Although I'm late to the party, for future readers: This minus sign (-) in `-?` is called a "modifier prefix", and what it does is, it removes the ? modifier of keys while remapping. Here's the link to the documentation: https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#mapping-modifiers — I've also added an explanation of how the `RequireAtLeastOne` type works in the next comment. – aderchox Jun 20 '23 at 09:06
  • 1. The Keys generic param is optional. If is provided, only those keys will produce a new object type in the final union, if is not provided, all keys of T will produce object types in the final union. 2. The first Exclude excludes all keys that are not in `Keys` param, and merges them in all the object types in the final union, without any changes to their ? modifier. 3. The -? removes the optional modifier on all the target keys. 4. `Required<...> & Partial<...>` finalizes the trick! The `Required` part makes the current key required, the `Partial` part makes all remaining keys optional. – aderchox Jun 20 '23 at 09:06
201

Not with a single interface, since types have no conditional logic and can't depend on each other, but you can by splitting the interfaces:

export interface BaseMenuItem {
  title: string;
  icon: string;
}

export interface ComponentMenuItem extends BaseMenuItem {
  component: any;
}

export interface ClickMenuItem extends BaseMenuItem {
    click: any;
}

export type MenuItem = ComponentMenuItem | ClickMenuItem;
ssube
  • 47,010
  • 7
  • 103
  • 140
  • Interesting. I have never exported a "type" before. So it simply means MenuItem can be one or the other ? – Nix Nov 09 '16 at 15:38
  • I like this better than the one I did below. Link that helped me understand "type" http://stackoverflow.com/a/36783051/256793 – Nix Nov 09 '16 at 15:41
  • Yes: TS has `AND` and `OR` operators for its types. If you're doing something like this, the OR can be nice to give you a single collected name after declaring a bunch of individual types. – ssube Nov 09 '16 at 15:42
  • and if you do it *just* right, the compiler may be able to infer which of the types you mean and narrow things correctly. Even if not, the compiler *will* verify the type you're passing meets at least one of the requirements. – ssube Nov 09 '16 at 15:42
  • 20
    Hi guys, how do you force to choose between one of the interfaces, but not both? With that type, an object with both `component` and `click` wont make an error... – Daniel Ramos Nov 14 '17 at 09:57
  • 16
    @DanielRamos you can add `click?: never` on `ComponentMenuItem` and `component?: never` on `ClickMenuItem`. – ethan.roday May 02 '18 at 20:55
  • @daniel i think that‘s because of this bug in typescript: https://github.com/Microsoft/TypeScript/issues/20863 – fabb Jan 24 '19 at 06:47
  • mmmmm and how am I supposed to implement a type? The point of making an interface is to make sure that the class will have certain methods and properties. In brief, I want my class to have whether one property or another, but there must be at least one of them. How do I do that? – Albert May 21 '20 at 13:06
  • Is there a way to do the same thing w/ just PropTypes for a project that doesn't have typescript? – Sheraff Oct 18 '20 at 07:21
  • I am getting only title and icon when i am using type MenuItem – Mohak Londhe Nov 06 '20 at 04:42
  • 15
    Any way to make this compatible with parameter destructuring? I get TS errors if I try `function myFunc({ title, icon, component, click }: MenuItem)` Property 'component' does not exist on type 'MenuItem'. Property 'click' does not exist on type 'MenuItem'. – CletusW Nov 19 '20 at 23:57
  • 2
    There's a library with a bunch of utility types from Sindresorhus known as type-fest. Link [here](https://github.com/sindresorhus/type-fest) – Leela Venkatesh K Jan 12 '22 at 08:14
129

There is a simpler solution. No need to rely on any or complex conditional types (see answer):

  1. Is there a way to require component or click to be set? (Inclusive OR)
type MenuItemOr = {
    title: string;
    icon: string;
} & ({ component: object } | { click: boolean }) 
// brackets are important here: "&" has precedence over "|"

let testOr: MenuItemOr;
testOr = { title: "t", icon: "i" } // error, none are set
testOr = { title: "t", icon: "i", component: {} } // ✔
testOr = { title: "t", icon: "i", click: true } // ✔
testOr = { title: "t", icon: "i", click: true, component: {} } // ✔

A union type (|) corresponds to inclusive OR. It is intersected with the non-conditional properties.

Use the in operator to narrow the value back to one of the constituents:

if ("click" in testOr) testOr.click // works 
  1. Is there a way to require that both properties can't be set? (Exclusive OR / XOR)
type MenuItemXor = {
    title: string;
    icon: string;
} & (
        | { component: object; click?: never }
        | { component?: never; click: boolean }
    )

let testXor: MenuItemXor;
testXor = { title: "t", icon: "i" } // error, none are set
testXor = { title: "t", icon: "i", component: {} } // ✔
testXor = { title: "t", icon: "i", click: true } // ✔
testXor = { title: "t", icon: "i", click: true, component: {} } //error,both set

Basically either component or click can be set, the other should never 1 be added at the same time. TS can make a discriminated union type out of MenuItemXor, which corresponds to XOR.

This XOR condition for MenuItemXor is not possible with accepted answer.


Playground

1 Technically, prop?: never gets resolved to prop?: undefined, though former is often used for illustration.

ford04
  • 66,267
  • 20
  • 199
  • 171
  • 11
    How to deal with `testOr.click;` that returns the error `Property 'click' does not exist on type 'MenuItemOr'.` ? – Eduardo Dallmann May 20 '20 at 02:30
  • 9
    @EduardoDallmann you can use the [`in` operator](https://www.typescriptlang.org/docs/handbook/advanced-types.html#using-the-in-operator) to check, if either properties exist on the object ([Playground](https://www.typescriptlang.org/play/#code/C4TwDgpgBAshB2BXAksCBbA8gJygXigG8BYAKCgqmAEtgAbCALigGdhtr4BzAbjMqjUAxgHt4zNh259SAXygAyKAApCUUejBiEwZknQAjCLnkAfIurrCA1swMiRDAIbwosgJRkyAEwhC6TtjQDMBUEGw4zHBIqBg4PFAA9IlQXNQAbgisIujQaBG46YHUTgYMUADutAAWsAgoaFi4oJBepNQAZioARP423YKu+cA47kT8lMM4AHR9QtZJKRUi2NYsExTJYQWzOVrwOotQxtgrZPJknT0a+zoDnNsj2GMk5JPhT7ua2vChW8urdZvTYpKbYWZWeZHE5nORAA)) – ford04 May 20 '20 at 08:07
  • Nice answer! But can you explain how it works? I have a hard time understanding the union (`|`) with only a right operand. Thank you – NorTicUs Aug 04 '20 at 09:14
  • 1
    @NorTicUs If you mean the leading `|` inside `& ( ... )` of `MenuItemXor`: this is just a shortcut for the union operator for better type formatting / separating over lines. Like [here](https://www.typescriptlang.org/play/#code/PTAECMHsBcAtQIYCcCmoDOCC2KCwAoaATwAc0AxSSUAXlANEdAB8NokBLAOwHMGnWXAK5ZwKJP0YFiZUJUgAmWm068WoYaPFA) - no magic involved :) – ford04 Aug 04 '20 at 09:22
  • 2
    It's slick but a slight loss in readability IMO compared to this answer: https://stackoverflow.com/a/61281828/2306481 – Sheraff Oct 18 '20 at 07:25
  • @Sheraff I guess, it boils down to usage of [interfaces vs type aliases](https://stackoverflow.com/questions/37233735/typescript-interfaces-vs-types). Personally, I prefer type aliases in most cases, as they can be used more flexibly than interfaces. It certainly is also a matter of taste/style. – ford04 Oct 18 '20 at 07:55
  • Any way to make this compatible with parameter destructuring? I get TS errors if I try `function myFunc({ title, icon, component }: MenuItemOr) {}` – CletusW Nov 19 '20 at 23:38
  • @CletusW You might define `component` additionally as weak property - either in the `MenuItemOr` type or if it's just for one case as parameter intersection `MenuItemOr & { component?: object }`, see [Playground](https://tsplay.dev/QmbebN). In this case, the destructured `component` property gets type `object | undefined`. – ford04 Nov 20 '20 at 00:29
  • Thanks for the helpful answer @ford04. I'm curious if you can look at my similar [playground](https://codesandbox.io/s/t0e1i?file=/index.ts:22-27) (lines 22-27) and see why the type checking doesn't work as I'd expect. What am I missing? – Jeremy Thomerson Nov 20 '20 at 01:53
  • @JeremyThomerson use `loc.url !== undefined` instead. A truthy check is not sufficient, as in the false case, `loc.url` might still be an empty string `""`, which does not fit the `LocalLocation` type constituent expecting `undefined`. – ford04 Nov 20 '20 at 07:20
  • Thanks @ford04! I got so focused on the truthy case, I totally forgot that `Boolean("")` is falsy – Jeremy Thomerson Nov 20 '20 at 15:09
  • 1
    the `in` operator recommendation was very helpful ( I wish optional chaining (`obj?.myProp`) also worked) – bristweb Jun 07 '23 at 20:21
21

An alternative without multiple interfaces is

export type MenuItem = {
  title: string;
  component: any;
  icon: string;
} | {
  title: string;
  click: any;
  icon: string;
};

const item: MenuItem[] = [
  { title: "", icon: "", component: {} },
  { title: "", icon: "", click: "" },
  // Shouldn't this error out because it's passing a property that is not defined
  { title: "", icon: "", click: "", component: {} },
  // Does error out :)
  { title: "", icon: "" }
];

I've asked a similar question at How to create a Partial-like that requires a single property to be set

The above could be simplified, but it may or may not be easier to read

export type MenuItem = {
  title: string;
  icon: string;
} & (
 {component: any} | {click: string}
)

Note that none of these prevent you from adding both because TypeScript does allow extra properties on objects that use AND/OR See https://github.com/Microsoft/TypeScript/issues/15447

Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
19

I use this:

type RequireField<T, K extends keyof T> = T & Required<Pick<T, K>>

Usage:

let a : RequireField<TypeA, "fieldA" | "fieldB">;

This makes fieldA and fieldB required.

LingSamuel
  • 496
  • 5
  • 10
  • For other readers: please note that this type will not make *one of* ( OR context ) the properties to be required, but rather both of them ( that said, in an AND context it will do just fine ). – Oleg Valter is with Ukraine Jan 15 '21 at 19:38
  • 1
    I had to tweak the solution a little bit to make it work here: `type RequireField = T | Required>`. I changed the intersection operator (`&`) to the union operator (`|`). – Dami May 25 '22 at 19:23
9

I ended up doing:

export interface MenuItem {
  title: string;
  icon: string;
}

export interface MenuItemComponent extends MenuItem{
  component: any;
}

export interface MenuItemClick extends MenuItem{
  click: any;
}

Then I used:

 appMenuItems: Array<MenuItemComponent|MenuItemClick>;

But was hoping there was a way to model it with a single interface.

Beau Smith
  • 33,433
  • 13
  • 94
  • 101
Nix
  • 57,072
  • 29
  • 149
  • 198
6

I like using Pick along with a base type that includes all properties to establish these kinds of conditional requirements.

interface MenuItemProps {
  title: string;
  component: any;
  click: any;
  icon: string;
}

export interface MenuItem =
  Pick<MenuItemProps, "title" | "icon" | "component"> |
  Pick<MenuItemProps, "title" | "icon" | "click">

This is clean and also flexible. You can get arbitrarily complex with your requirements, asserting things like "require either all the properties, just these two properties, or just this one property" and so on while keeping your declaration simple and readable.

Dane
  • 874
  • 12
  • 18
6

Here's a simple way to implement either but not both

type MenuItem =  {
  title: string;
  component: any;
  click?: never;
  icon: string;
} | {
  title: string;
  component?: never;
  click: any;
  icon: string;
}

// good
const menuItemWithComponent: MenuItem = {
  title: 'title',
  component: "my component",
  icon: "icon"
}

// good
const menuItemWithClick: MenuItem = {
  title: 'title',
  click: "my click",
  icon: "icon"
}

// compile error
const menuItemWithBoth: MenuItem = {
  title: 'title',
  click: "my click",
  component: "my click",
  icon: "icon"
}
dmwong2268
  • 3,315
  • 3
  • 19
  • 20
2

Yet another solution:

type RequiredKeys<T, K extends keyof T> = Required<Pick<T, K>> & Omit<T, K>;

type MenuItem2 = RequiredKeys<MenuItem, "component" | "click">;

1

This approach combines never and Omit. Benefits here are that it's easy to understand and also easy to update if you need to add more properties.

interface Base {
  title: string;
  icon: string;
  component?: never;
  click?: never;
}

interface OnlyComponent {
  component: any;
}

interface OnlyClick {
  click: any;
}

export type MenuItem = (Omit<Base, 'component'> & OnlyComponent) | (Omit<Base, 'click'> & OnlyClick);

You can use in to narrow an instance of MenuItem:

const item: MenuItem = {
  title: 'A good title';
  icon: 'fa-plus';
  component: SomeComponent;
};

//...

if('component' in item) {
  const Comp = item.component;
  //...
}

Wayne Baylor
  • 159
  • 3
  • 1
    The `never` option seems to also be useful for excluding certain properties when defining an interface with string-enumerable indexes, e.g. `interface Columns{ [column: string]: any; columns?: never; }` to prevent confusion with a wrapping class `interface ColumnData{ columns: Columns; }` (real example; I kept missing places I needed to unwrap before passing to functions, but this should help). The only concern I might have is whether, at runtime, I might end up with a column named `columns`; but since Typescript compiles to JavaScript, this type-checking should be lost by runtime, correct? – Trevortni Feb 16 '22 at 20:47
0

To just extends upon the cool answers above! And for the people that land here while searching for a Partial version with requiring capability! Here a snippet i made to take!

PartialReq

You want to have a Partial of an interface, but in mean time require some of the fields! Here how it's done

export type PartialReq<T, Keys extends keyof T = keyof T> =
    Pick<Partial<T>, Exclude<keyof T, Keys>>
    & {
        [K in Keys]: T[K]
    };

Use example

export interface CacheObj<SigType = any, ValType = any> {
    cache: Map<SigType, ValType>,
    insertionCallback: InsertionCallback<SigType, ValType> // I want this to be required
}

// ...

export class OneFlexibleCache<SigType = any, ValType = any> {
    private _cacheObj: CacheObj<SigType, ValType>;

    constructor(
        cacheObj: PartialReq<CacheObj<SigType, ValType>, 'insertionCallback'> // <-- here
                                                                           //  i used it
    ) {
        cacheObj = cacheObj || {};

        this._cacheObj = {

// ...

// _______________ usage
this._caches.set(
    cacheSignature,
    new OneFlexibleCache<InsertionSigType, InsertionValType>({
        insertionCallback // required need to be provided
    })
);

Here you can see that it work perfectly

enter image description here

If the required not provided

enter image description here

UPDATE: For the usage that i implied above here a better answer

I just went by the doc and found Omit.

https://www.typescriptlang.org/docs/handbook/utility-types.html#omittk

I came to add it. But before i do, I just seen this cool answer. It cover all:

https://stackoverflow.com/a/48216010/7668448

Just check it out! It show how to do it for all the different version of Typescript! And for the sake of not repeating ! Go and check!

Mohamed Allal
  • 17,920
  • 5
  • 94
  • 97