Accessing Unknown Objects in TypeScript
Posted:There's a utility function I find myself adding to almost every TypeScript repo I work on.
It goes like this:
const isObject = (
unknown: unknown
): unknown is Record<PropertyKey, unknown> =>
typeof unknown === 'object' && unknown !== null
# Usecase in Error Handling
Occasionally we have to deal with unknown
values. The most common example of this is in catch
blocks. Often we want to assume that the caught error is some kind of native Error
object, which would have a message
property.
try {
somethingDangerous()
} catch (error) {
// `error` is of `unknown`
console.log(error.message)
// We get a type error on `error.message`
// because `Object is of type 'unknown'`
}
Since TypeScript 4.4 caught errors are
unknown
by default.
But by using our isObject
utility, we can narrow the error
to something which has keys or not:
try {
somethingDangerous()
} catch (error) {
console.log(
isObject(error)
? error.message
// 👆 safe access within this branch
: error
// In this case `error` is something different
// So we'll just log it directly as is.
)
}
# Usecase in Composing Type Predicates
Learning to create your own type predicates is an important skill in TypeScript. There's a lot you can do by hand, but I will very often use isObject
as a starting point for more involved type predicates.
Here's a very simple example:
type UserApiResponse = {
userName: string
}
const isUserApiResponse = (
userResponse: unknown
): userResponse is UserApiResponse =>
isObject(userResponse)
&& typeof userResponse.userName === 'string'
If we need to go any deeper than one level, this approach becomes very cumbersome; at which point I recommend reaching for a schema validation library such as Zod.
Here's how the same code above would look with Zod:
import { z } from 'zod'
const UserApiResponse = z.object({
userName: z.string()
})
type UserApiResponse = z.infer<typeof UserApiResponse>
const isUserApiResponse = (
userResponse: unknown
): UserResponse is UserApiResponse =>
UserApiResponse.safeParse(userResponse).success
That's all for today.
Take care,
Rupert