根据TypeScript中的现有对象创建TypeSafe对象文字

时间:2018-07-04 14:41:07

标签: typescript types

考虑以下功能:

function keysToValuesClone<TObject extends {}>(keysAndValuesObj: TObject) {
    const clone = Object.assign({}, keysAndValuesObj);

    Object.keys(clone).forEach(key => clone[key] = key);

    const frozen = Object.freeze(clone);

    return (<any>frozen) as Readonly<{ // The `any` is regrettable... see below
        [K in keyof typeof clone]: K
    }>;
}

我可以为该函数提供一个对象常量,如下所示:

const myHeaders = keysToValuesClone({
    'a property with spaces': '',
    'and another': '',
});

产生的myHeaders将具有以下类型:

Readonly<{
    'a property with spaces': "a property with spaces";
    'and another': "and another";
}>

我喜欢此功能的实用程序,但编写它却引起了两个问题:

是否有可能失去any

如果我将keysToValuesClone的最后一行更改为此:

    return frozen as Readonly<{
        [K in keyof typeof clone]: K
    }>;

然后我遇到以下错误:

Type 'Readonly<TObject & {}>' cannot be converted to type 'Readonly<{ [K in keyof (TObject & {})]: K; }>'.
  Type '(TObject & {})[P]' is not comparable to type 'P'.

我尝试以多种不同的方式说服编译器,以允许该函数不使用any类型断言,但是所有尝试均失败。这是可以改善的吗?您能否说服编译器顺其自然,而无需诉诸any

是否可以创建一个类似的函数来从string[]构造这种类型?

我很想写这段代码:

const myHeaders = keysToValuesUsingArray([
    'a property with spaces',
    'and another'
]);

即提供一个字符串数组,并使其输出与上述相同。从代码上来说,我可以这样实现:

function keysToValuesArray(keys: string[]) {
    const obj = keys.reduce((objInTheMaking, key) => ({ [key]: key, ...objInTheMaking  }), {});

    return obj;
}

但这会产生类型{}。在类型系统中是否有办法了解这里发生的事情?

我玩过keyof / typeof / Tuple等都无济于事。因此,我的朋友们求助于您。

教育我。


感谢提香!

太棒了,提香;非常感谢!我最终通过Readonly调整介绍了您的阵列解决方案。见下文:(我现在Object.freeze真是爱不释手,这是我的麻烦)

function keysToValuesArray<T extends string>(keys: T[]): Readonly<{ [P in T] : P}> {
    const obj = keys.reduce((objInTheMaking, key) => ({ [key]: key, ...objInTheMaking  }), {} );

    const frozen = Object.freeze(obj);

    return frozen as Readonly<{ [P in T] : P}>;
}

再次感谢!

1 个答案:

答案 0 :(得分:2)

如果我们使用通用类型参数表示字段名称,那么可以确保从string数组创建对象:

function keysToValuesArray<T extends string>(keys: T[]) : { [P in T] : P}{
    const obj = keys.reduce((objInTheMaking, key) => ({ [key]: key, ...objInTheMaking  }), {} );
    return obj as { [P in T] : P};
}
const myHeaders2 = keysToValuesArray([
    'a property with spaces',
    'and another'
]);

为避免强制转换为any,可以直接将clone键入为any{ [n: string]: any },但这不会好得多。在构建这些对象时,它们无论如何都不符合类型,因此并没有太大的区别。我通常对呼叫站点是类型安全的想法感到安慰,这更重要。

function keysToValuesClone<TObject extends {}>(keysAndValuesObj: TObject) {
    const clone : { [n: string]: any } = Object.assign({}, keysAndValuesObj);

    Object.keys(clone).forEach(key => clone[key] = key);

    const frozen = Object.freeze(clone);

    return frozen as Readonly<{ 
        [K in keyof typeof clone]: K
    }>;
}