7

Consider that I have the following type:

type SomeType = {
  propOne: any;
  propTwo: any;
  propThree: any;
}

The propOne is required, propTwo and propThree are optional but at least one of them is required. How can I define the type with that constraint?

// The following code is my expectation
let someVar1: SomeType = { propOne: 1, propTwo: "two" } //Okay
let someVar2: SomeType = { propOne: 1, propThree: "three" } //Okay
let someVar3: SomeType = { propOne: 1, propTwo: "two", propThree: "three" } //Okay
let someVar4: SomeType = { propOne: 1 } //Not Okay
Trí Phan
  • 1,123
  • 2
  • 15
  • 33
  • Which TS version do you use? Because in newest 3.8 only third case is correct (no TS error) – Marek Szkudelski Mar 27 '20 at 07:56
  • 2
    @MarekSzkudelski that's OP's expected outcome. – VLAZ Mar 27 '20 at 07:57
  • Possible duplicate of https://stackoverflow.com/questions/48230773/how-to-create-a-partial-like-that-requires-a-single-property-to-be-set/48244432 – Ehsan Mar 27 '20 at 07:58
  • I would create a constructor to enforce this "check constraint" and throw an exception if the parameters do not meet the expectations. – DaggeJ Mar 27 '20 at 07:59
  • @DaggeJ that relies on this type being a class. However, it's possible to merely construct and consume it separately without involving classes in the process. Also, a constructor will enforce the check *only* at construction time, not later if the instance is modified. – VLAZ Mar 27 '20 at 08:01
  • @VLAZ: Sounds legit :) – DaggeJ Mar 27 '20 at 08:15

2 Answers2

14

You can use the same trick even after your edit:

type SomeType = {
  propOne: number;
  propTwo?: string;
  propThree?: string;
} & ({
  propTwo: string;
} | {
  propThree: string;
})

let someVar1: SomeType = { propOne: 1, propTwo: "two" } //Okay
let someVar2: SomeType = { propOne: 1, propThree: "three" } //Okay
let someVar3: SomeType = { propOne: 1, propTwo: "two", propThree: "three" } //Okay
let someVar4: SomeType = { propOne: 1 } //Not Okay

function f(s: SomeType) {
  let p1 = s.propOne; // number
  let p2 = s.propTwo; // string | undefined
  let p3 = s.propThree; // string | undefined
}

It forces you to have at least one property at declaration time, and it allows you to use both properties of the type when using objects of that type.

Playground

Tiberiu Maran
  • 1,983
  • 16
  • 23
  • Thank you for your fast response, it solved my problem, but it will lengthen my code if I have the more complex types. – Trí Phan Mar 27 '20 at 08:17
  • You can probably use the answer from the duplicate thread, something like this: `type SomeType = { propOne: any } & AtLeastOne<{ propTwo: any, propThree: any }>`. Then you only declare your properties once – Tiberiu Maran Mar 27 '20 at 08:22
  • Can this be done with interfaces? – John Dec 06 '22 at 18:44
3

Here is type that is union type of 2 objects:

type SomeType = {
    propOne: any;
} | {
  propTwo: any;
}

Typescript will require object's type to be compatible to one of type in this union. Here is solution in playground

Note: This is the simplest solution if you really have only two properties.

Marek Szkudelski
  • 1,102
  • 4
  • 11