Skip to main content

Command Palette

Search for a command to run...

TypeScript's [number] Index Signature: Extract Union Types from Arrays

Updated
3 min read
TypeScript's [number] Index Signature: Extract Union Types from Arrays
G

Full stack engineer but passionnated by front-end Angular Expert / NX / JavaScript / Node / Redux State management / Rxjs

Ever needed to create a union type from an array of objects in TypeScript? There's an elegant solution using the [number] index signature notation that can save you from maintaining duplicate type definitions.

The Problem

Let's say you have an array of API status objects and need to create types from them:

const API_STATUSES = [
  { code: 'success', message: 'Request completed', httpCode: 200 },
  { code: 'error', message: 'An error occurred', httpCode: 500 },
  { code: 'loading', message: 'Request in progress', httpCode: null },
  { code: 'timeout', message: 'Request timed out', httpCode: 408 }
] as const;

// How do we get types from this array?

You could manually define union types, but that means maintaining the same information in two places:

// Manual approach - duplication!
type ApiStatusCode = 'success' | 'error' | 'loading' | 'timeout';
type ApiStatusObject = {
  code: ApiStatusCode;
  message: string;
  httpCode: number | null;
};

The Solution : [number] Index Signature

The [number] notation extracts the type of elements that can be accessed with any numeric index:

const API_STATUSES = [
  { code: 'success', message: 'Request completed', httpCode: 200 },
  { code: 'error', message: 'An error occurred', httpCode: 500 },
  { code: 'loading', message: 'Request in progress', httpCode: null },
  { code: 'timeout', message: 'Request timed out', httpCode: 408 }
] as const;

// Extract the union of all object types
type ApiStatusObject = (typeof API_STATUSES)[number];

// Extract specific property types
type ApiStatusCode = ApiStatusObject['code']; // 'success' | 'error' | 'loading' | 'timeout'

How It Works

Breaking down (typeof API_STATUSES)[number]:

  1. typeof API_STATUSES - Gets the type of the array
  2. [number] - Accesses the type of elements at any numeric index
  3. Result - A union of all object types in the array

The [number] essentially says "give me the type of whatever can be found at any number index in this array."

Practical Example

Here's how you might use this in practice:

const API_STATUSES = [
  { code: 'success', message: 'Completed', retryable: false },
  { code: 'error', message: 'Failed', retryable: true },
  { code: 'loading', message: 'In progress', retryable: false },
  { code: 'timeout', message: 'Timed out', retryable: true }
] as const;

type ApiStatusObject = (typeof API_STATUSES)[number];
type ApiStatusCode = ApiStatusObject['code'];

function getStatusInfo(code: ApiStatusCode): ApiStatusObject | undefined {
  return API_STATUSES.find(status => status.code === code);
}

function isRetryable(code: ApiStatusCode): boolean {
  const status = getStatusInfo(code);
  return status?.retryable ?? false;
}

// TypeScript knows these are valid
console.log(isRetryable('error')); // true
console.log(isRetryable('success')); // false

// TypeScript error - invalid status code
// console.log(isRetryable('invalid')); // ❌
// Argument of type '"invalid"' is not assignable to parameter of type '"success" | "error" | "loading" | "timeout"'.(2345)

Why This Matters

Single source of truth: Your array serves as both runtime data and type definition. Add a new status object to the array, and the types update automatically.

Type safety: TypeScript catches typos and invalid values at compile time.

Better refactoring: Rename a property in your array objects, and TypeScript will help you find all the places that need updating.

Conclusion

The [number] index signature is a simple but powerful TypeScript feature. It eliminates the need to manually maintain union types that mirror your array data, giving you better type safety with less code duplication.

Next time you find yourself writing the same information in both an array and a type definition, remember this elegant solution.

TypeScript

Part 1 of 1