I agree with @ABOS, that in your case it can be useful to consider augmenting of global types, however, I understand, that some times it is not an option - strict eslint rules, team code guides etc ...
I know only one solution to type correctly Array.prototype.reduce in your particular case:
const keys = ['A', 'B', 'C'] as const;
type T = { [key in typeof keys[number]]: boolean; };
const t1: T = { A: true, B: true, C: true }; // OK
const t2: T = { A: true, B: true }; // Type error
const result = keys.reduce<T>((a, v) => ({ ...a, [v]: true }), {} as T) // T
You just need to explicitly cast initial value of reduce. This problem is also related to typing of setState
method in react. See open issue here.
Regarding to Array.prototype.map
.
In your case you can type your mapping function in next way:
type Mapped<Arr extends ReadonlyArray<unknown>, Result extends ReadonlyArray<unknown> = []> =
Arr extends []
? [] : Arr extends [infer H]
? readonly [...Result, readonly [H, true]] : Arr extends readonly [infer Head, ...infer Tail]
? Mapped<readonly [...Tail], readonly [...Result, readonly [Head, true]]> : Readonly<Result>
type Result = Mapped<MyArray>; // readonly [readonly ["A", true], readonly ["B", true], readonly ["C", true], readonly ["D", true]]
/**
* Because arrays are mutable in JS,
* you are unable to type result variable as Result directly
*
* One thing you can do is to use cast operator - as
* I hope you don't mutate your arrays inside [map] callback
*/
const result = keys.map<Result[number]>((s) => ([s, true] as const)) as unknown as Result
Regarding fromEntries
.
Same as Object.keys
, fromEntries
will not return correctly typed keys of source objects, it made purposely by TS team, because, like I already said, JS mutable nature.
You have two ways here, the way which was introduced by @ABOS, or type casting - through as
operator.
For example, there is a common pattern of using Object.keys
:
const obj = { foo: 1, bar: 2 }
const keys = Object.keys(obj) as Array<keyof typeof obj> // ("foo" | "bar")[]
Please keep in mind, it is still workaround, it is not 100% type safe, because of mutable nature of JS.
Here you can find more about Object.keys
I know, you did not ask about, Object.keys, but it is still same problem as fromEntries
Summary:
There are two ways: augmenting global type definition or using type casting. Each way is not completely safe, but if you are not mutating your arrays/objects it is ok to use it.
P.S. I believe there are more ways to achieve it, just because I prefer functional programming, I did not provide any example with mutable data structures, but if you are ok with mutating, @kaya3 solution is good