I'm trying to define a function that swaps values of two properties on an object given their names, but I would like compiler to check types compatibility (or at least check both properties to have same type):
function swap<T, TKey1 extends keyof T, TKey2 extends keyof T>(obj: T, key1: TKey1, key2: TKey2): void{
let temp = obj[key1];
obj[key1] = obj[key2];
obj[key2] = temp;
}
let obj = {
a: 1,
b: 2,
c: ""
}
swap(obj, "a", "b"); // good, both are numbers
swap(obj, "a", "c"); // should not compile, swapping number with string
I got some result with the following, but it requires obj to be passed twice.
function swap<T,
TKey1 extends keyof T,
TKey2 extends keyof T,
TIn extends { [p in TKey1|TKey2]: T[TKey1] } >(_:T, obj: TIn, key1: TKey1, key2: TKey2): void{
let temp = <any>obj[key1];
obj[key1] = <any>obj[key2];
obj[key2] = temp;
}
let obj = {
a: 1,
b: 2,
c: ""
}
swap(obj, obj, "a", "b"); // good, both are numbers
swap(obj, obj, "a", "c"); // error, as expected
Alternatively, I can achieve desired result with conditional types if I return a function, but it is too easy to forget second call.
function swap<T,
TKey1 extends keyof T,
TKey2 extends keyof T>(obj: T, key1: TKey1, key2: TKey2):
T[TKey1] extends T[TKey2] ? T[TKey2] extends T[TKey1]
? () => void
: never : never {
return <any>(() => {
let temp = <any>obj[key1];
obj[key1] = <any>obj[key2];
obj[key2] = temp;
});
}
let obj = {
a: 1,
b: 2,
c: ""
}
swap(obj, "a", "b")(); // good, both are numbers
swap(obj, "a", "c")(); // error, as expected
Is it possible to simplify above examples? Can I maybe supply some type instead of never
that will indicate error to type system?
P.S. I know about [obj.a, obj.b] = [obj.b, obj.a];
, but would like to avoid it.
Ok, they key turned out to be filtering the second key with advanced types.
Source code available: https://github.com/IKoshelev/ts-typing-util/blob/master/src/Swap.ts
NPM installation npm i ts-typing-util
export type SwappableKeys<T, TKey1 extends keyof T> = Exclude<{
[key in keyof T]:
/**/ T[key] extends T[TKey1]
/**/ ? T[TKey1] extends T[key]
/* */ ? key
/* */ : never
/**/ : never;
}[keyof T], TKey1>;
/**
* Swap prop values with a check that values have compatible type
* @example
* const t = {
* a: 1,
* b: 2,
* c: '',
* c1: '',
* d: { a: 5 },
* e: { a: 6 },
* f: { b: 7 },
* g: { a: '' }
* }
*
* swap(t, 'a', 'b');
* swap(t, 'a', 'c'); //error
* swap(t, 'b', 'c'); //error
* swap(t, 'a', 'a'); //error
* swap(t, 'c', 'c1');
* swap(t, 'd','e');
* swap(t, 'd','f'); //error
* swap(t, 'd','g'); //error
**/
export function swap<T, TKey1 extends keyof T>(inst: T, key1: TKey1, key2: SwappableKeys<T, TKey1>): void {
const buff = inst[key1] as any;
inst[key1] = inst[key2] as any;
inst[key2] = buff;
}