I'm renaming your types to make them more conventional:
interface One {
b: boolean;
c: number;
}
interface Two {
d: string[];
f: boolean[];
}
One of the issues here is that types in TypeScript are extendible and not exact. So the value {b: true, c:2, d:["as"]}
is a perfectly valid instance of One | Two
, since it is assignable to One
. Normally you'd get excess property checking to warn you about d
, but since d
is part of Two
, the compiler won't see that as an excess property.
One idea would be to use a mapped type to say "something with none of the properties of T", like this:
type NoneOf<T> = { [K in keyof T]?: never };
So then NoneOf<One>
would be {b?: never, c?: never}
and NoneOf<Two>
would be {d?: never, f?: never}
. Then the type you're looking for would be
type DesiredType = (One | NoneOf<One>) & (Two | NoneOf<Two>);
And you could see it work:
const a: DesiredType = {b: true, c:2, d:["as"]}; // error
const okay: DesiredType = {b: true, c: 2}; // okay
But you said you wanted to have N interfaces, like say
interface Three {
g: number[];
h: string;
}
And you want something programmatic, I guess, that turns (One | Two | Three | Four | ...
) into (One | NoneOf<One>) & (Two | NoneOf<Two>) & (Three | NoneOf<Three>) & (Four | NoneOf<Four>) & ...
. This is possible, use a variant of a solution to turn unions into intersections:
type UnionToAllOrNone<U> = (U extends any
? (k: U | NoneOf<U>) => void
: never) extends ((k: infer I) => void)
? I
: never;
And then let's see it work with One | Two | Three
:
type I = UnionToAllOrNone<One | Two | Three>;
function f(i: I) {}
f({}); // none of them
f({ b: true, c: 1 }); // just One
f({ d: [""], f: [true] }); // just Two
f({ g: [1], h: "" }); // just Three
f({ b: true, c: 1, d: [""], f: [true] }); // One and Two
f({ b: true, c: 1, d: [""], f: [true], g: [1], h: "" }); // One, Two, Three
Those are all accepted, while the following is rejected:
f({ b: true, c: 1, d: [""] }); // error!
/*
Argument of type '{ b: true; c: number; d: string[]; }'
is not assignable to parameter of type '(One & Two & Three) |
(One & Two & NoneOf<Three>) | (One & NoneOf<Two> & Three) |
(One & NoneOf<Two> & NoneOf<Three>) | (NoneOf<One> & Two & Three) |
(NoneOf<One> & Two & NoneOf<...>) |
(NoneOf<...> & ... 1 more ... & Three) |
(NoneOf<...> & ... 1 more ... & NoneOf<...>)
*/
The error message might be a bit cryptic, since it basically is a union of 2-to-the-power-of-N intersections, but hopefully it's good enough.
Okay, hope that helps; good luck!
Link to code