Kaustubh Badrike
Kaustubh Badrike

Reputation: 565

How would I enforce at-least one in many optional properties for a TypeScript interface?

I want to declare an interface for objects which may have either property 'a' or 'b'

interface myInterface {
    a ?: string;
    b ?: string;
}

makes both a and b optional. I want something like

interface myInterface {
    a|b ?: string;
}

(Edit1) Extended case

interface myInterface 
{ 
    mustHaveProp1 : number;
    a|b : string;
    mustHaveProp2 : string;
    b|d|e : number;
}

Upvotes: 4

Views: 969

Answers (2)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249616

You can't do this with an interface, you can do it with unions. The idea is to generate a union where we each union constituent has the same properties as the original type, but one property is required, so for myInterface we need something like: { a: string, b?: string } | { a?: string, b: string }.

The above union is also (mostly) equivalent to the following intersection myInterface & ({ a : string } | { b: string }. (If we apply the distributive property between & and | we would get (myInterface & { a: string }) | ((myInterface & { b: string }), and since in each intersection a property appears as both optional and required, the required wins out).

The big question then becomes how to create this union starting from myInterface without having to write it out explicitly. To do this, we can use a mapped type to take each property from an original type an create each member constituent and then index into the type to get the union of all of these new types. We can then just intersect with the original type:

interface myInterface {
  a?: string;
  b?: string;
}

type OneProp<T> = {
  [P in keyof T]-?: Record<P, T[P]>
}[keyof T]

type RequireAtLeastOne<T> = T & OneProp<T>

let o1: RequireAtLeastOne<myInterface> = {} // err no properties
let o2: RequireAtLeastOne<myInterface> = { a: "" } // a present, ok
let o3: RequireAtLeastOne<myInterface> = { b: "" } // b present, ok
let o4: RequireAtLeastOne<myInterface> = { a: "", b: "" } // a and b present, ok

Playground Link

Upvotes: 5

Ron BELLAICHE
Ron BELLAICHE

Reputation: 3

type IModal = { content: string; content_object?: undefined } | { content_object: object; content?: undefined }

Upvotes: 0

Related Questions