#post
@typescript-mutually-exclusive-xor-type
- copy in my Without and XOR code
- link to the stack overflow post I got it from
- Link to the TS issue discussing it
- Link to the conditional types docs mentioned in the SO post
- TS missing a built type helper for easily making a union type mutually exclusive
- For that, you need each type to set the excluded properties as optional never
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
// A custom XOR type that declares two types to be mutually exclusive
// Used when we want to ensure we only pass one of a pair of related params to an endpoint
// Example: XOR<{ a: string }, { b: string }> becomes { a: string, b?: never } | { a?: never, b: string }
// see: https://stackoverflow.com/a/53229567/8802485
export type XOR<T, U> = T | U extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
Alternative that uses Partial
:
// see: https://stackoverflow.com/questions/42123407/does-typescript-support-mutually-exclusive-types#comment123255834_53229567
// see: https://www.typescriptlang.org/play/?#code/LAKALgngDgpgBAVQHYEsD2SDSMIGcA8AKgHxwC8chcMAHmDEgCa6VwD8cA1jmgGasAuOEhgA3GACcA3KFAB6OXACSAWygS04uCiT0ANnpS4Gx0JFhwAojSgBDJkVIUqtekxZUOAbzgBtTNpIXDz8hAC6QoT+YXAAvnBCIuLSsuDQ8ADyIhm8RNR0DMxwXrG+YU7FoHB+ATrBEHyUEVY29oxE0XAAZHAACrYSYCi2evgASjAAxmgS7daTegCujDD4yOhYOARRSIsqAEaS5QA09Y1RmCfCYpLEdzIgpbsHRw+gOvQSvLaT8ADCtjAlRA1WqRgBYCEYAkixgD1iqQ+km+vzgABE0ABzYGg7S4DGYqEwuGgBEgd66ZE-eAAIRQsxxoKMdNmRNh8NS5ngAEFUCoRuQ4FkYDl8L4IacCacWYxyg8FKCAHpsVLTJC4IGTQFCXkofl6QU+cHauDQ2FxB5qjVwRhYnV8gUUI34u2m4kW0BWzWAgn2vWO4p4iFsmCnIy+t3m2JSOAKyQaCSgIA
type UnionKeys<T> = T extends T ? keyof T : never;
// Improve intellisense
type Expand<T> = T extends T ? { [K in keyof T]: T[K] } : never;
type OneOf<T extends {}[]> = {
[K in keyof T]: Expand<T[K] & Partial<Record<Exclude<UnionKeys<T[number]>, keyof T[K]>, never>>>;
}[number];
interface Cat {
isCat: true;
}
interface Dog {
isDog: true;
}
interface Bird {
isBird: true;
}
type Animal = OneOf<[Cat, Dog, Bird]>;
// ^?
const cat: Animal = { isCat: true };
const dog: Animal = { isDog: true };
const catDog: Animal = { isCat: true, isDog: true }; // error
Simpler alternative that uses Omit
, which removes the property instead of setting it to never:
// see: https://stackoverflow.com/a/72858846/8802485
export type Either<A, B> = Omit<A, keyof B> | Omit<B, keyof A>;
- related: maninak/ts-xor: Compose object types containing mutually exclusive keys, using this generic Typescript utility type.
- related: TypeScript: Documentation - TypeScript 2.8 - Introduction of conditional types
- Proposal: Add an “exclusive or” (^) operator · Issue · microsoft/TypeScript
The problem with this is that both properties in the pair can be undefined
at the same time. We want only one to be (when going the “include but set to undefined” route):
export type PerturbationsEndpointParams = {
concentration: string;
concentrationSf: string;
filterTags: string;
geneExpressionThreshold: string | undefined; // must include, but ok if value is undefined
groupLabel: string;
ignoreGeneThreshold: 'true' | 'false';
includeControlWells: 'true' | 'false';
pairwise: 'true' | 'false';
perturbations: string;
pvalue: string | undefined; // must include, but ok if value is undefined
pvalueByPertType: string | undefined; // must include, but ok if value is undefined
search: string;
splitBy: string;
url?: string;
zfpkmByTimepoint: string | undefined; // must include, but ok if value is undefined
};