avatarAlex Efimenko

Summarize

TypeScript Index Signatures: 4 Examples Type-Safe Dynamic Objects

Let’s explore 4 examples of typing objects with unknown structures

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.

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:

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

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

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).

Image from Unsplash

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.

// 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.

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 as I wrote here:

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

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.

Image generated by Dall-e

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.

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.

Image generated by Foocus

Enjoyed the read? Hit 👏 like it’s a high-five — to motivate me to bring more stories!

I’m always looking to meet new people in tech. Feel free to connect with me on LinkedIn!

If you enjoyed this article, consider trying out the AI service I recommend. It provides the same performance and functions to ChatGPT Plus(GPT-4) but more cost-effective, at just $6/month (Special offer for $1/month). Click here to try ZAI.chat.

Typescript
Oop
Web Development
JavaScript
Programming
Recommended from ReadMedium