The compiler raises a concern regarding the assignment of boolean
to the properties of Obj
; only the reverse assignment is guaranteed. This issue arises due to the existence of true
and false
literal types, which are more precise than boolean
. If a property has a type of true
, assigning a generic boolean
value may lead to compatibility issues since it can be false
. Similarly, if the property type is false
, assigning any boolean
value might result in a mismatch with actual values. Therefore, assigning a boolean
value to acc[cur]
triggers a warning from the compiler.
Consider this example:
const x = { a: true, b: false } as const;
/* const x: { readonly a: true; readonly b: false; } */
const y = arrayToBoolIndicator(x, ["b"]);
// const y: { readonly a: true; readonly b: false; }
console.log(y); // {a: false, b: true}, indicating an issue
(y.a && "abc").toUpperCase(); // compiles successfully but throws a runtime error!
In this scenario, x
is initialized with constant assertions for its properties: a
being true
and b
being false
. However, when calling arrayToBoolIndicator(x, ["b"])
, the expected output doesn't align with the actual implementation—resulting in the a
property of y
becoming false
during runtime. Consequently, the compiler permits (y.a && "abc").toUpperCase()
as if y.a
were still
true</code, potentially leading to issues at runtime.</p>
<p>The compiler error stemming from <code>arrayToBoolIndicator()
serves as a cautionary flag for such complications. While this might not pose a real-life problem frequently, it underpins the current issue.
To address this situation, simply specify that the accumulator's property types are exclusively boolean
, distinct from Obj
's property types:
const arrayToBoolIndicator = <Obj extends Record<string, boolean>>(
boolData: Obj,
includedKeys: (keyof Obj)[]
) => {
return objectKeysTyped(boolData).reduce<Record<keyof Obj, boolean>>(
// specify type argument --> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(acc, cur) => {
acc[cur] = includedKeys.includes(cur);
return acc;
},
{ ...boolData }
);
};
This approach manually specifies the type argument within the reduce()
method as Record<keyof Obj, boolean>
, ensuring acc[cur]
anticipates a boolean
.
By doing so, you eliminate the previous risk:
const x = { a: true, b: false } as const;
/* const x: { readonly a: true; readonly b: false; } */
const y = arrayToBoolIndicator(x, ["b"]);
// const y: const y: Record<"b" | "a", boolean>
(y.a && "abc").toUpperCase(); // now prompts a compiler error!
// ----------> ~~~~~~~~~~~
// Property 'toUpperCase' does not exist on type 'false | "abc".
With this adjustment, although the properties of x
retain boolean literals, the output of the function y
possesses a type of {a: boolean, b: boolean}
, making y.a && "abc"
no longer assumed to be strictly a string
. The compiler rightfully flags the call to a potential non-existent toUpperCase()
method.
Playground link to code