How does one create a mapped type from an object? If I attempt the following:
interface Stats {
count: number,
avg: number
}
class Blah<T> {
private stats: { [K in keyof T]: Stats };
constructor(foo: T) {
this.stats = Object
.keys(foo)
.reduce((result, key) => Object.assign(result, {
[key]: { count: 0, avg: 1 }
}), {});
}
}
...I get the error:
Type '{}' is not assignable to type '{ [K in keyof T]: Stats; }'
This appears to be because the initial value in reduce
is does not match the interface.
I can change my declaration this as a workaround:
private stats: { [K in keyof T]?: Stats };
...but that now means that some values for a keyof T may be undefined according to the type.
How do you create a fully mapped type, given the inability to create a mapped object without intermediate results in JavaScript?
It's a bit tedious, but you can do the following:
interface Stats {
count: number,
avg: number
}
type StatsMap<T> = { [K in keyof T]: Stats };
class Blah<T> {
public stats:StatsMap<T>;
constructor(foo: T) {
this.stats = Object
.keys(foo)
.reduce<StatsMap<T>>((result, key) => Object.assign(result, {
[key]: { count: 0, avg: 1 }
}), {} as any);
}
}
const blah = new Blah({ a: 'a', b: 'b', c: 'c' });
console.log(blah.stats.a.avg); // Yay, no error + autocomplete!
The {} as any
is required to tell TypeScript you know what you're doing :-/
You can test this on the playground.
Note that I made the stats
memboer public
to show the usage! This is not required or anything. You also do not have to create the StatsMap
, but I find it easier to read compared to writing { [K in keyof T]: Stats }
multiple times.