TypeScript Index Signatures: 4 Examples Type-Safe Dynamic Objects
Tags: typescript, webdev, javascript, node
Published: 2024-03-13 01:25:07

In TypeScript, **index signatures** are a powerful feature that allows you to type objects with unknown structures.

They are especially useful when you want to define a type for objects with unknown properties or when you want to create a dictionary-like data structure.

Additionally, index signatures are often used to create complex utility types that can be used to manipulate and transform other types.

## What are Index Signatures?

An index signature defines a type for the values that an object can have at a particular index (key). It specifies a contract between the keys and the values of an object.

```typescript
interface MyInterface {
  [key: string]: number;
}
```

In this example, the index signature `[key: string]` defines that any key of type `string` will have a corresponding value of type `number`.

This means that any object implementing the `MyInterface` interface can have any number of string keys, and the values associated with those keys must be numbers.

## Example 1: String-to-String Dictionary

Let’s say you want to create a simple dictionary that maps language codes (like “en”, “fr”, “es”) to their full English names (“English”, “French”, “Spanish”). With an index signature, you can define a type for this dictionary that allows for any number of language codes as keys, but ensures that all values are strings.

Define the Dictionary interface:

```typescript
interface LanguageDictionary {
  [key: string]: string;
}
```

Now we are sure that any string can be used as a key and all values are also strings:

```typescript
const languages: LanguageDictionary = {
  en: "English",
  fr: "French",
  es: "Spanish",
  // You can add more languages without changing the type
};

// Adding a new language to the dictionary
languages.de = "German";

// Retrieving a language name
console.log(languages.en); // Output: "English"
```

## Example 2: Product Inventory Object

Suppose you are building an e-commerce application and you want to represent a product inventory object that has a fixed set of properties (name, price) and a dynamic set of properties (stock for different sizes).

You can use an index signature to define a type for this object that allows for a fixed set of properties and a dynamic set of properties.

To maintain strict type safety without mixing specific properties with index signatures when their types differ, it’s better to separate the concerns.

```typescript
// Bad example of mixing properties with index signatures
type BadProduct = {
  name: string;
  price: number;
  // Error - Property 'name' of type 'string' is not assignable to 'string' index type 'number'
  [size: string]: number; 
}


// Better solution - It's immediately clear which parts of the object are fixed and which parts are dynamic.
type Product = {
  name: string;
  price: number;
  stock: {
    [size: string]: number;
  };
}
```

In this example the `name` and `price` fields maintain their strict types, separate from the dynamically typed `stock` object.

```typescript
const tShirt: Product = {
  name: 'T-Shirt',
  price: 20,
  stock: {
    'S': 10,
    'M': 15,
    'L': 5,
  },
};

// Accessing the 'M' stock value using nested object with bracket notation
console.log(product1.stock['M']); // Output: 15
```

This pattern is scalable and can be extended to include other dynamic properties if needed, by adding more nested objects or arrays with their specific types.

## Example 3: Creating a Custom Utility Type

Index signatures are essential in TypeScript for creating complex utility types because they allow for flexible and dynamic data structures while maintaining type safety.

Suppose you have a type representing a user with several properties, and you want to create a new type where all of these properties are optional.

There is built-in utility type `Partial` that does exactly that. Earlier, [I wrote about TypeScript utility types](https://medium.com/@alexefimenko/7-highly-effective-typescript-utility-types-9770b5360821).

However, for understanding of index signatures, let’s create a custom utility type `Optional` that does the same thing.

```typescript
type User = {
  id: number;
  name: string;
  age: number;
  email: string;
};

type Optional<T> = {
  [K in keyof T]?: T[K];
};

type OptionalUser = Optional<User>;

// OptionalUser is equivalent to:
// type OptionalUser = {
//   id?: number | undefined;
//   name?: string | undefined;
//   age?: number | undefined;
//   email?: string | undefined;
// }
```

In the above example, we created a custom utility type `Optional` that takes a type `T` and returns a new type where all properties of `T` are optional.

Here we use index signatures to iterate over the keys of `T` and make each property optional by adding a `?` after the property name.

This allows us to create a new type `OptionalUser` that has all the properties of `User` but with each property being optional.

## Example 4: API response with dynamic keys

When working with APIs, you often receive data with a fixed set of properties and a dynamic set of properties. Index signatures are useful for defining types for such data.

Suppose you have an API that returns a response with a fixed set of properties (status, message) and a dynamic set of properties (data for different resources).

You can use an index signature to define a type for this response that allows for a fixed set of properties and a dynamic set of properties.

```typescript
type ApiResponse = {
  status: string;
  message: string;
  [resource: string]: any;
};

const response: ApiResponse = {
  status: "success",
  message: "Data fetched successfully",
  users: [{ id: 1, name: "John" }, { id: 2, name: "Doe" }],
  products: [{ id: 1, name: "Laptop", price: 1000 }],
  // You can add more resources without changing the type
};
```

In this example, the `ApiResponse` type has a fixed set of properties (status, message) and an index signature `[resource: string]` that allows for any number of dynamic properties with any value type.

## Conclusion

Index signatures are a powerful feature in TypeScript that allows you to define types for objects with unknown structures. They are especially useful when you want to create dictionary-like data structures or when you want to define complex utility types.

I hope this article has given you a good understanding of how to use index signatures in TypeScript and how they can be used to create complex and flexible types. If you have any questions or feedback, feel free to leave a comment below.

Check out my other TypeScript articles:
- [Making React Components More Flexible with TypeScript Generics: 3 Examples](https://dev.to/alexefimenko/3-examples-of-typescript-generic-react-components-4f9)
- [5 Resources Each TypeScript Developer Should Know About](https://dev.to/alexefimenko/5-resources-each-typescript-developer-should-know-about-30m4)
- [TypeScript Enums: 5 Real-World Use Cases](https://dev.to/alexefimenko/typescript-enums-5-real-world-use-cases-4idk)


This article was originally [posted on Medium](https://medium.com/@alexefimenko/typescript-index-signatures-build-type-safe-dynamic-objects-0082dc3372c9).