Typescript Neither, Either, but Not Both Properties

Viewed 908

I'd like to assert that a type can have neither, either, but not both of a pair of properties.

The following does not work, although I'd have assumed it would. Apparently having a: never and b: never means that I have to provide both?

I also tried using {} in place of NeitherANorB which compiled and ran, but did not catch the fourth case where I incorrectly passed both options.

interface JustA {
    a: string;
    b: never;
}

interface JustB {
    a: never;
    b: string;
}

interface NeitherANorB {
    a: never;
    b: never;
}

type NotBothAAndB = JustA | JustB | NeitherANorB;

function testMe(x: NotBothAAndB) {
    console.log(x);
}

testMe({}); // OK
testMe({ a: "HI!" }); // OK
testMe({ b: "SUP?" }); // OK
testMe({ a: "HI!", b: "SUP?" }); // NOT OK
1 Answers

You can use {property?: never} or {property?: undefined} to say that a property should not be present. Note that even in the former case, the property is allowed to be present so long as it has the value undefined, since that is just how Typescript handles optional properties. So this should do what you want:

type NotBoth =
    | {a: string, b?: never}
    | {a?: never, b: string}
    | {a?: never, b?: never}

// ok
const test1: NotBoth = {a: 'foo'};
const test2: NotBoth = {b: 'bar'};
const test3: NotBoth = {};
// error: 'string' is not assignable to 'undefined'
const testFail: NotBoth = {a: 'foo', b: 'bar'};

Playground Link

For convenience, here's a helper type for constructing unions like this, where at most one of some properties is allowed:

// Simplify<T> just makes the resulting types more readable
type Simplify<T> = T extends infer S ? {[K in keyof S]: S[K]} : never
type NoneOf<T> = Simplify<{[K in keyof T]?: never}>
type AtMostOneOf<T> =
    | NoneOf<T>
    | {[K in keyof T]: Simplify<Pick<T, K> & NoneOf<Omit<T, K>>>}[keyof T]

type NotBoth = AtMostOneOf<{a: string, b: string}>

Playground Link

Related