Generic and discriminated types?
Hey an idea came to mind but I don't know how to type it or even if it is possible at all.
How can we define the SomeType
type so the first
property can be anything of the T
but the second
property can't be the same as first
?
type SomeType<T> = {
first: T; // any option based on T
second: T; // everything on T except what `first` is using
};
// Example:
const value: SomeType<1 | 2 | 3> = {
first: 1,
second: 2 // can be 2 or 3 but not the same as `first`
};
Here's a solution using a distributive conditional type and Exclude
. The type parameter _T
is the same as T
but doesn't get distributed.
type SomeType<T, _T = T> = T extends unknown ? {
first: T;
second: Exclude<_T, T>;
} : never
Playground Link
Note that this only works correctly when T
is a union type; you can't use e.g. SomeType<string>
to get an object type requiring two distinct string properties.
I'm not sure that you can do this strictly with types, but you could use a functional abstraction to accomplish the kind of validation that you're asking about.
After creating the validator function from the initial union type, it will require that, for the argument object provided when invoking it, the type of the first
property is assignable the initial union type, and the type of the second
property is assignable to the union type (excluding the first).
The primary benefit of this approach is that the resulting value will be the exact type of the object passed in, rather than the second property being all of the union excluding the first.
TS Playground
function makeValidator <T>() {
return <First extends T, Second extends Exclude<T, First>>(input: { first: First; second: Second; }) {
return input;
};
}
const validate = makeValidator<1 | 2 | 3>();
validate({first: 1, second: 1}); /*
^^^^^^
Type '1' is not assignable to type '2 | 3'.(2322) */
validate({first: 1, second: 2}); // { first: 1; second: 2; }