For example I have such function:
type A = {
propA1: string
propA2: string
}
type B = {
propB1: string
propB2: string
}
const f = (arg: A | B) => { }
then It can be used like this:
f({propA1: 'val', propA2: 'val', propB1: 'val'})
But I need an error in this case. I would like possibility passed argument with type only A, or only B, without mixing.
Neither jabba's solution or yours is perfect. In jabaa's it's easy to miss it: Playground, in yours you can provide other properties as undefined
, like propB1: undefined
.
So the task is more complex than looks on the surface. Unfortunately I didn't start with premise of allowing overlapped properties. Neither I knew whether any other properties (not belonging to the types) are allowed, so I assumed allowed and rejected only properties from other types. This is generic allowing any number of types. If no other properties are allowed I hope it's easy to tweak.
So the solution:
type A = {
propA1: string
propA2?: string
}
type B = {
propB1: string
propB2: string
}
type C = {
propC1: string
propC2: string
}
type Union = A | B | C;
type IsUnion<T, C extends T = T> = (T extends T ? C extends T ? true : unknown : never) extends true ? false : true;
type Strict<T extends U, U extends object> = IsUnion<U extends unknown ? Extract<keyof U, keyof T> extends never ? never : U : never> extends true ? never : T;
const f = <T extends Union>(arg: Strict<T, Union>) => { }
f({ propA1: 'propA1', prop1: 12}); // ok, a required prop + extra
f({ propA1: 'propA1', propB1: 'propB1'}); // error, props from several union members
f({ propA2: 'propA2', propB1: 'propB1'}); // error, no valid union member is provided
So what happens:
U extends unknown ? Extract<keyof U, keyof T> extends never ? never : U : never
- here we check each union member whether it contains any keys from the provided input, Extract<keyof U, keyof T>
- if it's never
that means there's no overlap of a union member's keys and the input's keys. If there's an overlap we add the union member to the filtered union.
SO we get the union filtered by the input's key. So we need to make sure that this union is formed from 1 member thus only 1 member provided in the input. For that we use IsUnion
utility.
Thus we ensure that input contains keys only from 1 union member plus any extra keys not found in the union's members.