I have the following objectify implementation:
type MappedObject<T extends readonly string[]> = {
[key in T[number]]: number;
};
function objectify<T extends readonly string[]>(array: T): MappedObject<T> {
return array.reduce(
(acc, term, index) => ({ ...acc, [term]: index }),
{}
) as MappedObject<T>;
}
With that implementation, when I call objectify asserting the first passed argument with as const, it returns a type-safe object, as you can see in the following example:
const obj = objectify(['a', 'b', 'c'] as const); // Notice the `as const` assertion.
obj.a;
obj.b;
obj.c;
obj.d; // Error! My code is safe. :)
Example of this "safe" behavior in the playground.
Sadly, however, when I remove that as const assertion, that type-safety goes away, which turns my code error-prone. Because of that "unsafe" returned type, I can even access properties that don't even exist in the returned object, as exemplified (obj.d) in the last line of the following example:
const obj = objectify(['a', 'b', 'c']); // No more `as const` assertion.
obj.a;
obj.b;
obj.c;
obj.d; // No errors! My code is error prone. :(
Example of this "unsafe" behavior in the playground.
The question is: Is using the as const assertion the only way to guarantee that type-safety? There is another way of achieve this same "safe" behavior?
My goal is to fully guarantee the code safety in the function implementation, avoiding any assertion when I invoke the function. I want to make the following code safe by only changing the function implementation, and not its calls.
// What I can do:
const obj = objectify(['a', 'b', 'c'] as const);
// What I want to be able to do:
const obj = objectify(['a', 'b', 'c']);
///// `obj` would be "safe" here.