profileRyan KesPGP keyI build stuffEmailGithubTwitterLast.fmMastodonMatrix

TypeScript Index Signatures

Introduction

An object in JavaScript can be accessed with a string to hold a reference to any other JavaScript object.

let foo: any = {}
foo["Hello"] = "World"
console.log(foo["Hello"]) // World
class Foo {
  constructor(public message: string) {}
  log() {
    console.log(this.message)
  }
}

let foo: any = {}
foo["Hello"] = new Foo("World")
foo["Hello"].log() // World
let obj = {
  toString() {
    console.log("toString called")
    return "Hello"
  },
}

let foo: any = {}
foo[obj] = "World" // toString called
console.log(foo[obj]) // toString called, World
console.log(foo["Hello"]) // World

Syntax

Declaring an index signature

let foo: { [index: string]: { message: string } } = {}

/**
 * Must store stuff that conforms to the structure
 */
/** Ok */
foo["a"] = { message: "some message" }
/** Error: must contain a `message` of type string. You have a typo in `message` */
foo["a"] = { messages: "some message" }

/**
 * Stuff that is read is also type checked
 */
/** Ok */
foo["a"].message
/** Error: messages does not exist. You have a typo in `message` */
foo["a"].messages

Interfaces

As soon as you have a string index signature, all explicit members must also conform to that index signature. A few Interface examples:

interface Foo {
  x: number
}
let foo: Foo = { x: 1, y: 2 }
// Directly
foo["x"] // number
// Indirectly
let x = "x"
foo[x] // number

String literals

String Literals can also be used in the declaration:

type Index = "a" | "b" | "c"
type FromIndex = { [k in Index]?: number }

const good: FromIndex = { b: 1, c: 2 }

// Error:
// Type '{ b: number; c: number; d: number; }' is not assignable to type 'FromIndex'.
// Object literal may only specify known properties, and 'd' does not exist in type 'FromIndex'.
const bad: FromIndex = { b: 1, c: 2, d: 3 }

Generics

Generics can also be used:

type FromSomeIndex<K extends string> = { [key in K]: number }