Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditional required properties in interface

Tags:

typescript

I'd like to type an object that can contain any property, but if it has any of the properties a, b or c, it must contain all of the properies a, b and c.

In other words: the object can contain any property, but if it contains a, b or c it must contain a, b and c.

let something: Something;

// these should be valid:
something = { a: 1, b: 1, c:, 1 };
something = { a: 1, b: 1, c:, 1, custom: 'custom' };
something = { custom: 'custom' };

// these should be invalid:
something = { a: 1 };
something = { a: 1, b: 1 };
something = { a: 1, c: 1 };
something = { b: 1 };
something = { a: 1, custom: 'custom' };

How can this be achieved?

Creating a union type with an object seems correct, but doesn't work:

interface RequiredProps {
    a: number;
    b: number;
    c: number;
}

type Something = RequiredProps | {};
like image 572
Flauwekeul Avatar asked Mar 13 '18 20:03

Flauwekeul


1 Answers

You can create a union type, between a type that allows a, b, c and an indexer, and one that forces a, b, and c if present to have type never.

 type Something = ({ a: number, b: number, c: number } & { [n: string]: any }) |
    ( { a?: never, b?: never, c?: never } &{ [n: string]: any });

If you have functions that return never it may be possible to assign the properties but for most practical cases this should work.

We could even create a generic type for this situation:

type RequireAll<TOptional, TRequired> = (TOptional & TRequired) | (Partial<Record<keyof TOptional, never>> & TRequired)
type Something = RequireAll<{a: number, b: number, c:number}, {[n:string]:any}>
like image 72
Titian Cernicova-Dragomir Avatar answered Nov 14 '22 22:11

Titian Cernicova-Dragomir