Rasmus Hansen
Rasmus Hansen

Reputation: 1573

How to limit properties in typescript objects to strictly defined properties

Given the following code:

interface t1 {
  prop1: string;
}

interface t2 {
  prop2: string;
}

type ts = t1 | t2;

const t: ts = {
  prop1: 'foo',
  prop2: 'bar'
}

How come the t object is valid? And is that possible to disallow, so t can only be either t1 or t2, but not both at the same time?

Code can be seen on the typescript playground here.

Upvotes: 1

Views: 359

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250366

If the interfaces are compatible (that is they don't have any properties that are of different incompatible types), Typescript will allow you to specify any property in the union type. We can define a type that ensures that the object literal will be compatible with only one of the interfaces, by adding to each type of the union type all properties from other types with the never type:

type Diff<T extends string, U extends string> = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T];  
type Exclude<T, TExclude> = Partial<Record<Diff<keyof TExclude, keyof T>, never>>
type ts = (t1 & Exclude<t1,t2>) | (t2 & Exclude<t2, t1>);

interface t1 {
    prop1: string;
    prop4: string;
    propCommon: string;
}

interface t2 {
    prop2: string;
    propCommon: string;
}
const t: ts = { prop1: 'foo',prop2: 'bar', propCommon: ''}
const tt2: ts = {prop2: '', propCommon: ''}
const tt1: ts = {prop1: '', propCommon: '', prop4: ''}

For 3 types the usage would be :

type ts = (t1 & Exclude<t1,t2> & Exclude<t1, t3>) 
    | (t2 & Exclude<t2, t1> & Exclude<t1, t3>)
    | (t3 & Exclude<t3, t1> & Exclude<t3, t2>);

We could also define some helper types to make this look a bit better:

type Exclusive2<T, TExclude> = T & Exclude<T,TExclude>
type Exclusive3<T, TExclude, TExclude2> = T & Exclude<T,TExclude> & Exclude<T,TExclude2>
type Exclusive4<T, TExclude, TExclude2, TExclude3> = T & Exclude<T,TExclude> & Exclude<T,TExclude2> & Exclude<T, TExclude3>
type ts = Exclusive3<t1, t2, t3> | Exclusive3<t2, t1, t3> | Exclusive3<t3, t2, t1>

Upvotes: 3

Related Questions