I am attempting to create a function that can switch the values of two properties on an object based on their names. I want the compiler to enforce type compatibility, ensuring that both properties have the same data 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"); // works fine since both are numbers
swap(obj, "a", "c"); // should not compile as it's swapping a number with a string
I managed to achieve some results with the following approach, but it necessitates passing 'obj' 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"); // works fine since both are numbers
swap(obj, obj, "a", "c"); // produces an error, which is expected
Alternatively, by leveraging conditional types and returning a function, I can accomplish the desired result. However, there is a risk of forgetting to make the 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
If it's possible, how could I simplify the above examples? Is there a way to provide a specific type instead of 'never' that would flag an error to the type system?
P.S. I am aware of the alternative method using '[obj.a, obj.b] = [obj.b, obj.a];', but I prefer to explore other options.