To tackle this issue, you might need a function that mimics the behavior of Object.fromEntries()
. However, when it comes to TypeScript typings for such a function, it may end up being too general for your specific requirements. You could potentially receive a type like Record<string, number>
instead of Record<I, number>
. If you are comfortable with using type assertions, you can opt for that approach:
const irandomized = Promise.all(
(Object.keys(is) as I[]).map((i: I) =>
asyncRandomize()
.then((v): [I, number] => [i, v])
)
).then(z => Object.fromEntries(z)) as Promise<Record<I, number>>;
Avoiding the usage of any
in this scenario is crucial.
If you prefer not to rely on type assertions within your code, one alternative is to create your custom function akin to Object.fromEntries()
, but with more specific typings (keeping in mind that some form of type assertion needs to exist inside the function's implementation). Here's a potential function that might align better with your needs:
type Entry = readonly [PropertyKey, any];
type ExtractSupertype<T, U> = T extends any ? [U] extends [T] ? T : never : never;
function fromEntries<E extends readonly Entry[]>(entries: E): {
[K in E[number][0]]: ExtractSupertype<E[number], readonly [K, any]>[1] // TS 4.0-
// [K in E[number] as K[0]]: K[1] // TS 4.1+, easier syntax
} {
const ret: any = {};
for (let entry of entries) {
ret[entry[0]] = entry[1];
}
return ret;
}
It's worth noting that once TypeScript 4.1 is released and introduces mapped type as
clauses, the ExtractSupertype
construct will become redundant.
You can encapsulate this functionality in a separate library. The primary aim is for fromEntries()
to convert a strongly-typed array or tuple of entries into a strongly-typed object:
const foo = fromEntries([
["x", Math.random()], ["y", new Date()], ["z", Math.random() < 0.5]
] as const);
/* const foo: {
x: number;
y: Date;
z: boolean;
} */
This method ensures that you obtain specific properties associated with distinct types, rather than just receiving a generic
Record<string, number | Date | boolean>
.
Armed with this function, you can proceed without resorting to unsafe type assertions:
const iKeys = ["a", "b", "c"] as const;
const irandomized = Promise.all(
iKeys.map((k) =>
asyncRandomize()
.then(v => [k, v] as const)
)
).then(fromEntries);
The modification made involved replacing is
with an array of its keys. Since no operation was being performed on the values, leveraging Object.keys(is)
returned a type of string[]
instead of
I[]</code. To simplify things, a strongly-typed tuple of keys was utilized directly.</p>
<p>You can now verify that the output of <code>irandomized
matches your expectations:
/* const irandomized: Promise<{
a: number;
b: number;
c: number;
}> */
Furthermore, confirming its runtime behavior is equally important:
irandomized.then(e => console.log(JSON.stringify(e)));
// {"a":0.9961594084980729,"b":0.015675814053288217,"c":0.1783156372032898}
Playground link for the code