The unknown type in TypeScript

Vinh Le
4 min readJan 17, 2022

--

The unknown mistery

When I first onboarded TypeScript world from JavaScript, one of the most confusing thing is the unknown type. Remember when your coworkers write something like:

const foo = () => bar as unknown as any // <- 🤷🏻‍♂️🤷🏻‍♂️🤷🏻‍♂️

Let’s take a look at TypeScript official documentation:

unknown is the type-safe counterpart of any

Anything is assignable to unknown, but unknown is not assignable to anything but itself and any without a type assertion or a control flow based narrowing.

Does this clarify or just further confuse you? I know we don’t like boring texts and documentation, so let’s translate those into actual code:

Notice that even after being assigned to a number-typed value, foo still has unknown type and thus cannot do number-specific stuffs:

const foo: unknown = 1
console.log(foo > 10) // <- ⛔️⛔️⛔️ foo is still unknown, so it cannot be compared to a number

You can however make the compiler happy by casting the type of `foo` to number like:

// Rest assure compiler, I know what I am doing with foo
console.log(foo as number > 10)

In this trivial example, there is not much risk because we already assigned foo = 1 in the first place. However, in more complex cases, casting the type like this might backfire us:

Alright that’s enough for the basics, now let me show you the most common real-world usages of the unknown type and how to safely handle it 💪💪💪

Handle external data sources

We need to be super careful getting and parsing these data sources such as 3rd party JSON responses.

An analogy for this is when you received a “gift” from some strangers. You almost always want to check it carefully before actually using.

Let’s say we want to fetch a dog data by its id from a 3rd party API:

const dog = await fetchDogByIdFromApi(1)

Could we know what type this dog have? 🤔 Most likely NOT before we could do some checks.

So what should we do? We should consider it to have unknown type ❓. By doing this, TypeScript will prevent us from freely assigning its value such as:

The compiler also does not allow us to carelessly read the data like:

const ourDogName = fetchedDog.name.toLowerCase() // 💣⛔️⛔️ <- this will explode if fetchedDog.name does not exist

Pretty useful isn’t it? You must have additional checks to be able to assign an unknown value to our known Dog-typed variable. This totally make sense. Otherwise what would be the benefits of TypeScript if we just freely assign a not-yet-known value to a variable? 😅 This is probably 1 of the most common source for the timeless error: TypeError: Cannot Read Property of Undefined.

So how could we safely handle unknowntype and let TypeScript be at ease? Here are 2 approaches that I often go to: typeguards and type assertions.

Typeguards

Typeguard is an expression — an additional check to assure TypeScript that an unknown value conforms to a type definition.

Let’s see how can we write a typeguard to safely use fetchedDog value as our own Dog:

In this typeguard, we are using type predicate — a special return types that tells TypeScript compiler that as long as value satisfies inner checks, it must have Dog type.

Note that it is safe to use typecast value as Dog in this case to access name property because we already checked type value === 'object' && 'name' in value

With this in place, we can totally treat the newbie fetchedDogas 1 of our own:

Type assertions

An alternative to typeguard is an assertion function to throw an error if the input does not conform to our Dog type:

This assertDog function does basically what isDog typeguard did earlier. The only difference is the signature where it throw an Error right away if the type is mismatched. Therefore, we don’t need an if/else statement in the calling code:

Key takeaways

unknown is a pretty great feature of TypeScript in my opinion. It proves extremely useful when we need to parse, well unknown, 3rd party data sources.

To summary what we have gone through in this blog:

  • Anything is assignable to unknown, but unknown is not assignable to anything but itself. So we can do const foo: unknown = 'foo', but we cannot do, out of the box, const bar: string = foo if foo is unknown.
  • We can force the compiler to trust that an unknown varible has a specific type: const bar: string = foo as string. However, typecast might backfire us if we are not mindful using it.
  • Typeguards or type assertions are really powerful expressions that perform runtime checks to guarantee the type of an unknown value. They are generally better and safer to use that typecasts.

That’s the end of this blog. I would love to hear your ideas and thoughts 🤗 Please jot them down bellow 👇👇👇

✍️ Written by

Vinh Le @vinhle95

👨🏻‍💻🤓🏋️‍🏸🎾🚀

Say Hello 🙌 on:

🔗 Twitter

🔗 Medium

🔗 LinkedIn

🔗 Github

--

--

Vinh Le

An engineer loves building digital product and sharing knowledge👨🏻‍💻💪🔥🎾