Issue
In TypeScript, I can define the properties of a type based on a readonly tuple with the following construct:
const keys = ['A', 'B', 'C'] as const;
type T = { [key in typeof keys[number]]: boolean; };
This behaves as expected:
const t1: T = {A: true, B: true, C: true}; // OK
const t2: T = {A: true, B: true}; // Type error
Now, let's say that I want to dynamically create an object of type T based on the keys array, e.g. using reduce() or ES2019's Object.fromEntries():
const t1: T = keys.reduce((a, v) => ({ ...a, [v]: true }), {}); // Type error
const t2: T = Object.fromEntries(keys.map(s => [s, true])); // Type error
In both cases, I need an explicit type assertion to stop the compiler from complaining:
const t1: T = keys.reduce((a, v) => ({ ...a, [v]: true }), {}) as T; // OK
const t2: T = Object.fromEntries(keys.map(s => [s, true])) as T; // OK
Can anyone suggest a way to declare and initialize an object of type T based on the keys array, without having to resort to an object literal or explicit type assertion?
Solution
The simplest solution is to use Object.create(null) instead of {} to initialise the object. This is cheating a bit, because Object.create has return type any which means you don't get a type error simply because the type isn't checked.
function makeObject<K extends PropertyKey, V>(keys: K[], value: V): Record<K, V> {
const obj: Record<K, V> = Object.create(null);
for(const k of keys) {
obj[k] = value;
}
return obj;
}
The upside is that because the object has null as its prototype, it doesn't inherit properties like 'toString' which might mess up code which expects dynamically-accessed properties to definitely have type V if they exist. If you explicitly do want to inherit those properties, you can use Object.create({}) instead.
Answered By - kaya3
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.