1

I am trying to define a type

type myType{
 ....
 counter?: number,
 maxCount?: number,
}

with counter being required if (and only if) maxCount is provided. What are the options to specify it in typescript?

GRZa
  • 629
  • 4
  • 16
  • 1
    Does this answer your question? [In Typescript how to make a property required if another optional property is set?](https://stackoverflow.com/questions/64874558/in-typescript-how-to-make-a-property-required-if-another-optional-property-is-se) – Heretic Monkey Sep 19 '22 at 15:52
  • 1
    Use a discriminated union type: https://css-tricks.com/typescript-discriminated-unions/ – Terry Sep 19 '22 at 15:53
  • 2
    You need a union type of some sort. [This](https://tsplay.dev/W4xlOW) is one way to do it. Does that meet your needs? If so I'll write up an answer; if not, what am I missing? (Please notify me via a mention of @jcalz if you reply) – jcalz Sep 19 '22 at 15:58
  • `counter` is optional and can be provided even if `maxCount` is not provided, correct? – kelsny Sep 19 '22 at 15:59
  • 1
    Oh, if that's true, then [this](https://tsplay.dev/Nakq2W) would be my suggestion. @GRZa, could you confirm (or deny)? – jcalz Sep 19 '22 at 16:07
  • In my specific case, counter should not be provided if maxCount is not, but I wanted to find out both cases. – GRZa Sep 19 '22 at 16:19

1 Answers1

1

You will need MyType to be a union type where one member of the union requires the relevant properties, while the other member either prohibits them or makes them optional, depending on the use case.

TypeScript doesn't have an "official" or direct syntax for prohibiting a property. Merely leaving the property out of the type definition doesn't really have this effect, especially with unions. See Why does A | B allow a combination of both, and how can I prevent it? for more information.

Instead, you can make the property optional with a value of the impossible never type. Since you cannot find a value of the never type, an optional property of type never can only meaningfully be satisfied by not including the property at all.

I'd recommend making a base type that your union members extend.

interface BaseMyType {
  someProp: string;
  counter?: number;
  maxCount?: number;
}

If you want to allow either both maxCount and counter or neither of them to appear, then you can do it like this:

interface MyTypeWithCounter extends BaseMyType {
  counter: number,
  maxCount: number
}

interface MyTypeWithoutCounter extends BaseMyType {
  counter?: never,
  maxCount?: never;
}

type MyType = MyTypeWithCounter | MyTypeWithoutCounter

And test it:

let myType: MyType;
myType = { someProp: "abc", counter: 123, maxCount: 456 }; // okay
myType = { someProp: "abc", maxCount: 456 }; // error
myType = { someProp: "abc", counter: 123, }; // error
myType = { someProp: "abc", }; // okay

On the other hand, if you just want to make sure that counter appears if maxCount does, and otherwise counter should be allowed but not required, then you can write it this way:

interface MyTypeWithMaxCount extends BaseMyType {
  counter: number,
  maxCount: number
}

interface MyTypeWithoutMaxCount extends BaseMyType {
  maxCount?: never;
}

type MyType = MyTypeWithMaxCount | MyTypeWithoutMaxCount

And test it:

let myType: MyType;
myType = { someProp: "abc", counter: 123, maxCount: 456 }; // okay
myType = { someProp: "abc", maxCount: 456 }; // error
myType = { someProp: "abc", counter: 123, }; // okay
myType = { someProp: "abc", }; // okay

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360