Reputation: 1198
I'd like to write a function of two arguments, each of which is an object. These objects must not share keys. How do I type this constraint?
// helpers
const a=1, b=2, c=3, d=4;
// how do I type this function?
function func(x, y) {/*...*/}
func({a, b, c}, {d}) // ok; no keys are shared
func({a, b}, {b, c}) // Error; key `b` is shared.
Alternatively posed: how do I constrain a type so that it does not contain keys of another type?
const foo = {a, b};
type X = typeof foo;
type NoKeysOf<S> = {/*... */}; // <- How do I constrin this type?
const bar: NoKeysOf<X> = {d}; // ok; no keys shared
const baz: NoKeysOf<X> = {b, c} // Error; `b` is shared
Upvotes: 4
Views: 349
Reputation: 15313
You can enforce this kind of constraint at compile-time with the never type.
declare function f<
A,
B extends { [K in keyof B]: K extends keyof A ? never : B[K] }
>(
a: A, b: B
): any
What we are saying here is that if K
, which is a key of B
, extends one of the keys of A
, its corresponding value in b
will be of type never
(hence, you could never construct it), otherwise, it'll just be B[K]
.
And this has exactly the desired behaviour:
const a = 1, b = 2, c = 3, d = 4;
f({ a, b, c }, { d }) // OK
f({ a, b }, { b, c }) // Error: Type 'number' is not assignable to type 'never'
Upvotes: 3
Reputation: 26170
Personally I would write a function findSameKeys
that returns all shared keys. It's up to you to also move the logic that follows into this function.
function findSameKeys(x, y) {
const xKeys = Object.keys(x);
return Object.keys(y).filter(k => xKeys.includes(k));
}
const o1 = { a: 1, b: 2, c: 3 };
const o2 = { x: 1, y: 2, z: 3 };
const sameKeys = findSameKeys(o1, o2);
if (sameKeys.length === 0) {
console.log('OK: no keys are shared');
} else {
const verb = sameKeys.length === 1 ? ' is ' : ' are ';
console.log('Error: keys ' + sameKeys + verb + 'shared');
}
Upvotes: 0