Index signatures are one of TypeScript’s most powerful features for handling dynamic object properties. Whether you’re working with API responses, configuration objects, or data mappings, understanding index signatures is crucial for writing type-safe TypeScript code.
In this comprehensive guide, we’ll explore everything you need to know about TypeScript index signatures, from basic concepts to advanced use cases.
Table of Contents
- What are Index Signatures?
- When to Use Index Signatures
- Advanced Index Signature Patterns
- Type Safety with Index Signatures
- Best Practices
- Common Gotchas and Solutions
- Integration with Existing TypeScript Features
- Conclusion
What are Index Signatures?
Index signatures allow you to define the type of properties that an object might contain without knowing the exact property names in advance. They’re particularly useful when working with objects that have dynamic keys.
Basic Syntax
interface StringMap {
[key: string]: string;
}
const languages: StringMap = {
en: 'English',
es: 'Spanish',
fr: 'French'
};
Code language: PHP (php)
In this example, [key: string]: string
is the index signature. It tells TypeScript that any string key in the object must have a string value.
When to Use Index Signatures
1. Dynamic Data Storage
interface Cache {
[key: string]: any;
}
const cache: Cache = {
user123: { name: 'John', age: 30 },
user456: { name: 'Jane', age: 25 }
};
Code language: PHP (php)
2. API Response Handling
interface APIResponse {
[endpoint: string]: {
data: any;
timestamp: number;
};
}
const apiCache: APIResponse = {
'/users': {
data: ['user1', 'user2'],
timestamp: 1234567890
}
};
Code language: PHP (php)
Advanced Index Signature Patterns
Mixed Properties
You can combine index signatures with specific properties:
interface ConfigObject {
name: string; // Required specific property
[key: string]: string | number; // Index signature
}
const config: ConfigObject = {
name: 'MyApp',
version: '1.0.0',
port: 3000 // This works because number is allowed in the index signature
};
Code language: PHP (php)
Multiple Index Signatures
TypeScript allows you to use both string and number index signatures:
interface Matrix {
[row: number]: {
[col: number]: number;
};
}
const matrix: Matrix = {
0: { 0: 1, 1: 2 },
1: { 0: 3, 1: 4 }
};
Code language: PHP (php)
Type Safety with Index Signatures
Using Generic Constraints
interface TypedCache<T> {
[key: string]: T;
}
const numberCache: TypedCache<number> = {
one: 1,
two: 2
// three: 'three' // Error: Type 'string' is not assignable to type 'number'
};
Code language: PHP (php)
Readonly Index Signatures
interface ReadOnlyConfig {
readonly [key: string]: string;
}
const settings: ReadOnlyConfig = {
theme: 'dark'
};
// settings.theme = 'light'; // Error: Cannot assign to 'theme' because it is a read-only property
Code language: PHP (php)
Best Practices
1. Be Specific with Value Types
Avoid using any
when possible:
// Bad
interface LooseObject {
[key: string]: any;
}
// Good
interface StrictObject {
[key: string]: string | number | boolean;
}
Code language: PHP (php)
2. Use Union Types for Better Type Safety
type ValidKeys = 'id' | 'name' | 'email';
interface UserData {
[K in ValidKeys]: string;
}
const user: UserData = {
id: '123',
name: 'John',
email: '[email protected]'
};
Code language: PHP (php)
3. Consider Record Type
For simple cases, consider using the built-in Record
utility type:
type Ports = Record<string, number>;
const serverPorts: Ports = {
http: 80,
https: 443
// ssh: '22' // Error: Type 'string' is not assignable to type 'number'
};
Code language: JavaScript (javascript)
Common Gotchas and Solutions
Property Checking
interface StringDict {
[key: string]: string;
length: number; // Error: Property 'length' of type 'number' is not assignable to string index type 'string'
}
Code language: PHP (php)
Solution:
interface StringDict {
[key: string]: string | number; // Allow both string and number
length: number; // Now this works
}
Code language: PHP (php)
Type Narrowing with Index Signatures
interface Config {
[key: string]: unknown;
}
function getConfig(config: Config, key: string): string {
const value = config[key];
if (typeof value === 'string') {
return value;
}
throw new Error(`Config value for ${key} is not a string`);
}
Code language: JavaScript (javascript)
Integration with Existing TypeScript Features
Mapped Types
type Optional<T> = {
[K in keyof T]?: T[K];
};
interface User {
name: string;
age: number;
}
type OptionalUser = Optional<User>;
// Equivalent to: { name?: string; age?: number; }
Code language: PHP (php)
Conclusion
Index signatures are a powerful TypeScript feature that enables type-safe handling of dynamic object properties. By following the best practices and patterns outlined in this guide, you can write more maintainable and type-safe code.
For more advanced TypeScript patterns, check out our guide on TypeScript Mapped Types or explore TypeScript Utility Types for additional type manipulation techniques.
Remember to always be specific with your types and use index signatures judiciously to maintain type safety while allowing for the flexibility needed in your applications.