Object Types (TypeScript)
Tags: programming, typescript, webdev
Published: 2023-03-02 09:55:59

## What you should know about Object Types in TypeScript
I will mainly talk about these two topics in this article.
1. Structural Typing
2. Discriminated Unions

This article is inspired by [Learning TypeScript](https://www.learningtypescript.com) written by Josh Goldberg. I highly recommend reading this book if you want to learn TypeScript more.

## What are Object Types and how to use them

If you declare a new object, TypeScript will consider it a new object type.

In real-world development, it’s common to utilize **Aliased Object Types**.

I intentionally use “type” instead of “interface” in this article because I expect some readers of this article may not know about “interface” yet. But the type will be interchangeable in most cases if you’ve already been familiar with the interface.

```typescript
type Vehicle = {
   wheels: number;
   plate: string;
   run: () => void;
}

const car: Vehicle = {
   wheels: 4,
   plate: "aa2-cc4",
   run: () => {console.log("This car runs very fast!")}
}
```

If objects’ properties are not precisely the same when you initialize objects with Type annotation, TypeScript yells at you.

```typescript
type Vehicle = {
   wheels: number;
   plate: string;
   run: () => void;
}

const runner: Vehicle = {
   run: () => {console.log("This runner runs very fast!")}
}
// Type '{ drive: () => void; }' is missing the following properties from type 'Vehicle': wheels, plate

const airplane: Vehicle = {
	wheels: 3,
	plate: "11b-334",
	run: () => {console.log("This plane runs to departure!”)},
	fly: () => {console.log("This plane flies very high!”)}
}
// Type '{ wheels: number; plate: string; run: () => void; fly: () => void; }' is not assignable to type 'Vehicle'. Object literal may only specify known properties, and 'fly' does not exist in type 'Vehicle'.
```

So far so good, right?

Okay, it’s a good time to dive into the TypeScript world deeply.

## Structural Typing

Please guess what happens in the example below:

```typescript
type Runner = {
	run: () => void;
}

type Vehicle = {
   wheels: number;
   plate: string;
   run: () => void;
}

const car: Vehicle = {
   wheels: 4,
   plate: "aa2-cc4",
   run: () => {console.log("This car runs very fast!")}
}

const startToRun = (arg: Runner) => {
	arg.run();
}

startToRun(car);
```

Will TypeScript alert you? What do you think?

The answer is **No**.

You can execute the code without any error messages and see the log "This car runs very fast!" in the console.

It is the concept of **Structural Typing**, which is described in the Learning TypeScript book as follows:

>
*TypeScript’s type system is structurally typed: meaning any value that happens to satisfy a type is allowed to be used as a value of that type.*
> 

In other words, TypeScript does not care if there are extra properties. But it matters if a given value can satisfy the properties of the type.

Let’s look back at the previous example again.

```typescript
type Runner = {
	run: () => void;
}

type Vehicle = {
   wheels: number;
   plate: string;
   run: () => void;
}

const car: Vehicle = {
   wheels: 4,
   plate: "aa2-cc4",
   run: () => {console.log("This car runs very fast!")}
}

const startToRun = (arg: Runner) => {
	arg.run();
}

startToRun(car);
```

In this example, the `car` variable is compatible to be an argument of the `startToRun` function because the `Vehicle` type can satisfy the `run` property in the `Runner` type.

Let me show one more example:

```typescript
type Runner = {
	run: () => void;
}

type Vehicle = {
   wheels: number;
   plate: string;
   run: string;
}

const car: Vehicle = {
   wheels: 4,
   plate: "aa2-cc4",
   run: "boom"
}

const startToRun = (arg: Runner) => {
	console.log(arg.run());
}

startToRun(car);
// Argument of type 'Vehicle' is not assignable to parameter of type 'Runner'.
	// Types of property 'run' are incompatible.
    // Type 'string' is not assignable to type '() => void'.
```

In the above example, the type of the run property in the Vehicle type is string that is a different type from the run property in the Runner type. Therefore, TypeScript says that Types of property 'run' are incompatible.

## Discriminated Unions

Once you understand how to use Object Types, you will want to use them with Union.

Please take a look at an example:

```typescript
type EBook = {
	title: string;
	megabyte: number;
}

type PaperBook = {
	title: string;
	pages: number;
}

type Book = EBook | PaperBook;

const book: Book = Math.random() > 0.5
	? {title: 'Enjoy cooking', megabyte: 200}
	: {title: 'Learning History', pages: 300};

book.title; // No warning

book.megabyte
// Property 'megabyte' does not exist on type 'Book'.
//  Property 'megabyte' does not exist on type 'PaperBook'.

book.pages
// Property 'pages' does not exist on type 'Book'.
//  Property 'pages' does not exist on type 'EBook'.
```

TypeScript warns when accessing the `megabyte` and `pages` properties because these properties will potentially not exist in the `Book` type.

If you want to access `megabyte` or `pages`, you should use the technique of “Narrowing” to narrow down the possibility of the existence of properties.

Here is an example:

```typescript
if("megabyte" in book) {
	book.megabyte; // No type error
} else {
	book.pages; // No type error
}
```

The logic for narrowing down types is the so-called **type guard**.

There is another solution for narrowing down Unions. You can add a property whose value indicates the object’s type to union-typed objects.

```typescript
type EBook = {
	title: string;
	megabyte: number;
	format: 'digital';
}

type PaperBook = {
	title: string;
	pages: number;
	format: 'paper';
}

type Book = EBook | PaperBook;

const book: Book = Math.random() > 0.5
	? {title: 'Enjoy cooking', megabyte: 200, format: 'digital'}
	: {title: 'Learning History', pages: 300, format: 'paper'};

if(book.format === 'digital') {
	book.megabyte; // No type error
} else {
	book.pages; // No type error
}
```

Perfect! You can safely access the properties that will potentially not exist by this technique.

The union-typed object with this shape is called a **discriminated union**. And the property that is used as an identifier of union typed objects is a **discriminant**.

Are you wondering why string values can be a type?

Good point. Actually, `'digital'` and `'paper'` in this code is literal types that are more specific versions of primitive types. Therefore, you can consider `'digital'` and `'paper'` as types in the above example code.

## Conclusion

**Object Types** are a powerful technique to take advantage of TypeScript features.

One important fact that you should keep in mind is that the type system in TypeScript is **Structural Typing** which means you can use any value satisfying a type as a value of that type. (It does not matter whether the value has extra properties and a different name of a type.)

When it comes to making a union of object types, you can add a **discriminant** to object types. And the shape of union typed objects that have **discriminant** is considered as a **discriminated union**.