如何定义一个按名称交换两个对象属性值并检查类型兼容性的 Typescript 函数?

How to define a Typescript function that swaps values of two object properties by name, with check of type compatibility?

我正在尝试定义一个函数,该函数在给定名称的情况下交换对象上两个属性的值,但我希望编译器检查类型兼容性(或至少检查两个属性是否具有相同类型):

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

TS playground

我得到了以下结果,但它需要传递两次 obj。

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

TS playground

或者,如果我 return 一个函数,我可以使用条件类型实现期望的结果,但是很容易忘记第二次调用。

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

TS playground

是否可以简化上述示例?我可以提供某种类型而不是 never 来指示类型系统错误吗?

P.S。我知道 [obj.a, obj.b] = [obj.b, obj.a]; ,但想避免它。

好的,他们的密钥原来是用高级类型过滤第二个密钥。

可用的源代码:https://github.com/IKoshelev/ts-typing-util/blob/master/src/Swap.ts

NPM 安装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;

}