I have read multiple posts and issues on here with respect to narrowing an object of type unknown in TypeScript. But I have not found this specific question or solution.
Most solutions I find state to do something like this.
const result: unknown = {
movieName: "test",
};
if (
typeof result === "object" &&
result !== null &&
"movieName" in result &&
typeof result.movieName === "string" // <-- Error Here
) {}
The error states
Property 'movieName' does not exist on type 'object'
How do I narrow it so that it knows that the unknown thing is an object which contains the property movieName? How do I access result.movieName for an unknown type?
edit: perhaps it's not possible? and the recommendation in the comments below of declaring it as a Record<string, unknown> may be the only way per this GitHub request?
TypeScript 4.9 will introduce support for in operator narrowing of unlisted properties, as implemented in microsoft/TypeScript#50666, as a fix for microsoft/TypeScript#21732 mentioned below. At that point the above code will just work without modification:
const result: unknown = {
movieName: "test",
};
if (
typeof result === "object" &&
result !== null &&
"movieName" in result &&
typeof result.movieName === "string" // <-- okay
) {
result.movieName.toUpperCase(); // <-- okay
}
Playground link to code
This is currently a missing feature in TypeScript. See microsoft/TypeScript#21732 for the relevant feature request. Right now when you use the in operator as a type guard like k in result, it only really does any meaningful narrowing if the type of result is a union where some members are known to have the key k and others are not. It does not assert the existence of a property with key k. So in your case, where the type of result is just object (narrowed from unknown), nothing meaningful happens.
The workaround I sometimes use is to write my own user-defined type guard function which behaves the way I want in to behave. For example:
function hasKey<K extends string, T extends object>(
k: K, o: T
): o is T & Record<K, unknown> {
return k in o;
}
Now, instead of writing k in result, I write hasKey(k, result):
if (
typeof result === "object" &&
result !== null &&
hasKey("movieName", result) &&
typeof result.movieName === "string" // okay
) {
result.movieName.toUpperCase(); // okay
}
This works how you want, and is arguably the closest you can get to type safety here, at least until something is done to address microsoft/TypeScript#21732 natively.
Playground link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With