Making a Typescript property dependent on the value of another property

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
};

Typescript playground example

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.

Typescript playground example