I recently ran into a situation where I wanted Typescript to require a certain property, only if the value of a different property was a specific value.
With some help from a coworker and some additional reading, I found a solution that uses a combination of Union and Intersection types.
Let's say I have a couple of contrived objects like this
let a = {
title: 'A title';
type: 'value';
value: 'This is a value';
};
let b = {
title: 'Another title';
type: 'nothing';
};
What I want is for the property value
to only be required when type == 'value'
.
To start, I declare a base type which contains the properties I always want to have:
type Base = {
title: string;
type: 'value' | 'nothing';
}
Then I can declare intersection types for each possible value of the type
property:
type ValueType = Base & {
type: 'value';
value: string;
};
type NothingType = Base & {
type: 'nothing';
value: never;
}
For ValueType
, we inherit all of the type properties of the Base
type but require a string value
property when type == 'value'
;
For NothingType
, we also inherit everything from Base
but specify that the value
property should never exist.
Then we create a union type that we'll use on the actual objects we declare:
type MyType = ValueType | NothingType;
Here are examples of objects that pass all of our type checks:
let good1: MyType = {
title: 'This is a good object',
type: 'value',
value: 'a value'
};
let good2: MyType = {
title: 'This is a good object',
type: 'nothing',
};
And here are some objects that throw type errors:
let bad1: MyType = {
title: 'This is a good object',
type: 'value',
// Here, we're missing the `value` property
};
let bad2: MyType = {
title: 'This is a good object',
type: 'nothing',
value: 'another value'
// Here, we shouldn't have a `value` property at all
};
Update
The aforementioned coworker also showed me a nice way to do the same as the above using Interfaces which gives you slightly nicer error messages.