UPDATE FOR TS4.9+
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
PREVIOUS ANSWER FOR TS4.8-
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