# A Practical Look At The "Unknown" Type In Typescript

> Ever wanted to better understand the unknown type? This blog post walks you through a practical setup and look at working with the unknown type.

- Canonical: https://dennisokeeffe.com/blog/2022-10-18-a-practical-look-at-unknown-in-typescript
- Published: 2022-10-18
- Updated: 2022-10-18
- Tags: beginner, typescript

The `unknown` type in TypeScript has been a pain point for those entering into the domain of TypeScript. For those who do not understand the purpose of `unknown` when coming across it for the first time (myself included), then what has been introduced as a way to remove the type un-safety of `any` can instead lead to losing typing safety through misuse of casting without type narrowing.

In this post, we will look at a practical example of using the `unknown` type in TypeScript.

This post was inspired by some recent work at our company to remove any mis-casting or use of `any` in the codebase, and the reference-point for that PR was [this article](https://mariusschulz.com/blog/the-unknown-type-in-typescript) by Marius Schulz in 2019.

Source code can be found [here](https://github.com/okeeffed/demo-practical-unknown-in-typescript)

## Prerequisites

1. Basic familiarity with [TypeScript](https://nextjs.org/docs/api-reference/create-next-app).
2. Basic familiarity with [tsup](https://tsup.egoist.dev/).
3. TypeScript set up on your local IDE is ideal.

## Getting started

We will create the project directory `demo-practical-unknown-in-typescript` and set up some basics:

```s
$ mkdir demo-practical-unknown-in-typescript
$ cd demo-practical-unknown-in-typescript
$ yarn init -y
$ yarn add -D typescript tsup @tsconfig/recommended
$ mkdir src
$ touch src/index.ts tsconfig.json
```

In `tsconfig.json`, add the following:

```json
{
  "extends": "@tsconfig/recommended/tsconfig.json"
}
```

Update `package.json` to look like this:

```json
{
  "name": "demo-practical-unknown-in-typescript",
  "version": "1.0.0",
  "main": "index.js",
  "author": "Your name here",
  "license": "MIT",
  "devDependencies": {
    "@tsconfig/recommended": "^1.0.1",
    "tsup": "^6.2.2",
    "typescript": "^4.7.4"
  },
  "scripts": {
    "start": "node dist/index.js",
    "dev": "tsup src/index.ts --dts --watch --format esm,cjs",
    "build": "tsup src/index.ts --dts --format esm,cjs --minify"
  }
}
```

Update `src/index.ts` to actively have a typing issue to check things are ready:

```ts
const fooStr: unknown = "foo";
fooStr.toUpperCase();
```

`tsup` won't type-check by default, but it will if we pass the `--dts` flag in.

Run `yarn dev` and you will see the following error:

```s
src/index.ts(2,1): error TS2571: Object is of type 'unknown'.
```

At this point, we have a local project set up and can begin to work through some practical examples of `unknown` in projects.

## Demystifying the unknown type

The [unknown type](https://www.typescriptlang.org/docs/handbook/2/functions.html#unknown) is a type that represents _any type_ but, but unlike `any`, it is not legal to do anything with an `unknown` type.

Therefore, it helps us introduce safety by narrowing the type down to a more specific type.

In the initial code that we added to `src/index.ts`, we get any error on attempting to use a string method on the `unknown` type.

We need to check that the type is a string before we can legally use the `toUpperCase` method. To remedy this, update the code to the following:

```ts
const fooStr: unknown = "foo";
if (typeof fooStr === "string") {
  fooStr.toUpperCase();
}
```

Now our code will compile! Although this first example is contrived, it is a good starting point to understand the `unknown` type.

## A more practical example

A more common example of `unknown` making its way into your daily life as a developer is with a `try/catch` block.

The `catch` block by default in typescript will set the error as an `unknown` type, so we need to narrow the type down to a more specific type.

```ts
try {
  throw new Error("foo");
} catch (e) {
  console.log(e.message);
}
```

Once again, if we check our logs, the compiler fails with the following:

```s
src/index.ts(9,15): error TS2571: Object is of type 'unknown'.
```

You may have seen (or even yourself used) the following pattern to remedy this:

```ts
try {
  throw new Error("foo");
} catch (e) {
  console.log((e as Error).message);
}
```

Casting will work, but it is not ideal as it removes the type safety that TypeScript provides.

If `e` was not actually an `Error` object, then we would be in a situation where we are trying to access a property on an object that may not exist.

Instead, we can use the `instanceof` operator to check that the error is an `Error` object:

```ts
try {
  throw new Error("foo");
} catch (e) {
  if (e instanceof Error) {
    console.log(e.message);
  } else {
    console.log("An error occured");
  }
}
```

Now our error is handled and we can be sure that the `message` property exists on the `e` object.

## Being a little more practical

Another use case for the `unknown` type is arbitrary data that is returned from an API.

For example, we may have a function that returns a `Promise` that resolves to an `unknown` type. We can emulate this in our code simply by writing an object and declaring it as `unknown`:

```ts
const json: unknown = {
  foo: "bar",
};

json.foo; // TypeError: Property 'foo' does not exist on type 'unknown'.
```

To get the type safety that we want, we can use [type predicates](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) to narrow in on the type.

```ts
const json: unknown = {
  foo: "bar",
};

type Json = {
  foo: string;
};

function isJsonType(json: unknown): json is Json {
  function hasValidShape(
    given: unknown
  ): given is Partial<Record<keyof Json, unknown>> {
    return typeof given === "object" && given !== null;
  }

  return hasValidShape(json) && typeof json.foo === "string";
}

if (isJsonType(json)) {
  json.foo; // string
}
```

Our `isJsonType` helper here is a type predicate that narrows the type down to `Json` if the `json` object has the correct shape and the `foo` property is a string.

But what happens for even larger object shapes? You could continue to extend the type predicate that we wrote above, or we could use a library like [io-ts](https://github.com/gcanti/io-ts) to decode the type for us and validate the type within our predicate function with minimal code.

## Using io-ts to narrow down an unknown to a known type

We need to install `io-ts` and `fp-ts` for this example:

```s
$ yarn add io-ts fp-ts
```

We can then update our `src/index.ts` file to look like this:

```ts
import * as t from "io-ts";
import { isRight } from "fp-ts/Either";

// ... omitted for brevity

const moreJson: unknown = {
  foo: "bar",
  baz: {
    qux: "quux",
  },
  bool: true,
};

const moreJsonType = t.type({
  foo: t.string,
  baz: t.type({
    qux: t.string,
  }),
  bool: t.boolean,
});

type ComplexJson = t.TypeOf<typeof moreJsonType>;

function isComplexJsonType(json: unknown): json is ComplexJson {
  return isRight(moreJsonType.decode(json));
}
if (isComplexJsonType(moreJson)) {
  moreJson.foo; // string
  moreJson.baz.qux; // string
  moreJson.bool; // boolean
  console.log("moreJson is ComplexJson");
} else {
  console.log("moreJson is not a ComplexJson");
}
```

We have created a new `ComplexJson` type that has more properties than our previous example.

We can then use `ts-io` to help create a way to decode our type and use the `isRight` function to validate that the `decode` value is a `Right` value (we won't touch the `Either` monad result stuff here, but if you are unfamiliar with functional programming then just know that `isRight` will help confirm if our type is decoded successfully and valid). Wrapping this up in a helper function `isComplexJsonType` allows us to use the type predicate to narrow down the type.

Throughout this tutorial, while `yarn dev` has been running, `tsup` has compiled our code and placed it in the `dist` folder. If we run `node dist/index.js` we will see the following output:

```s
# Run the compiled code
$ node dist/index.js
moreJson is ComplexJson
```

Cool, so it looks like our `moreJson` object adheres to the `ComplexJson` type requirements.

If we update our code to comment out the `bool` property:

```ts
import * as t from "io-ts";
import { isRight } from "fp-ts/Either";

// ... omitted for brevity

const moreJson: unknown = {
  foo: "bar",
  baz: {
    qux: "quux",
  },
  // bool: true,
};

const moreJsonType = t.type({
  foo: t.string,
  baz: t.type({
    qux: t.string,
  }),
  bool: t.boolean,
});

type ComplexJson = t.TypeOf<typeof moreJsonType>;

function isComplexJsonType(json: unknown): json is ComplexJson {
  return isRight(moreJsonType.decode(json));
}
if (isComplexJsonType(moreJson)) {
  moreJson.foo; // string
  moreJson.baz.qux; // string
  moreJson.bool; // boolean
  console.log("moreJson is ComplexJson");
} else {
  console.log("moreJson is not a ComplexJson");
}
```

...then we will see the following output when we run the compiled code again:

```s
# Run the compiled code
$ node dist/index.js
moreJson is not a ComplexJson
```

In this way, we can confirm that our narrowing function has worked and we can be sure that our `moreJson` object adheres to the `ComplexJson` type requirements.

## Summary

Today's post was a review of the `unknown` type and to to see how we can use it to help us write safer code.

We looked at type narrowing, type predicates, and how we can use `io-ts` to help us decode our types and validate them.

I personally have misused `unknown` before by overriding the benefits with type casting, so it has been nice to review and write about the more common use cases for the `unknown` type. May you not make the same mistakes that I did when first encountering the type!

## Resources and further reading

- Basic familiarity with [TypeScript](https://nextjs.org/docs/api-reference/create-next-app).
- Basic familiarity with [tsup](https://tsup.egoist.dev/).
- [Source code](https://github.com/okeeffed/demo-practical-unknown-in-typescript)
- [The Unknown Type In Typescript - Marius Schulz](https://mariusschulz.com/blog/the-unknown-type-in-typescript)
- [io-ts](https://github.com/gcanti/io-ts)
- [Type predicates](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates)
- [The `unknown` type](https://www.typescriptlang.org/docs/handbook/2/functions.html#unknown)

_Photo credit: [`karsten116`](https://unsplash.com/@karsten116)_
