Which type to use for objects when properties are not known ahead of time in TypeScript?

January 8, 2022 - 4 min read

When the object properties are not known ahead of time but only the object's key type and value type (aka object structure), we can make use of index signatures in TypeScript.

TL;DR

// Index signature
// where `key` type is `string`
// and `value` type is `number`
interface Details {
  [key: string]: number;
}

// a simple function that gets
// some details of the user
function getDetails(details: Details) {
  console.log(details);
}

// call the function
getDetails({ salary: 200000, age: 23 }); // ✅ Valid.
getDetails({ age: 23 }); // ✅ Valid.
getDetails({ name: "John Doe" }); // ❌ Error. Type 'string' is not assignable to type 'number'.

To understand what index signatures is, let's consider an example of a function that get some details from the user through an object parameter like this,

// a simple function that gets
// some details of the user
function getDetails(details) {
  console.log(details);
}

Here we have a problem where we do not know the correct property names and the types the details object should hold. So we cannot create a normal type using an interface or type alias.

But the only information we have is that every key in the details object will be a string type and the value will be a number type. So with this information, we can create something called an index signature in TypeScript.

To create an index signature,

  • Firstly, we can either use an interface or type alias declaration or even an anonymous type declaration.
  • Then inside the declaration body, we can write a square bracket symbol ([])
  • Inside those square brackets let's write a variable name to denote the key (let's write it as key only!) followed by the colon symbol (:) then the write the type every key in the object should hold. In our case, it is the string type.
  • After the brackets, we can add a colon symbol (:) and then write out the type for property's value should hold. In our case, it is the number type.

It may look like this,

// Index signature
// where `key` type is `string`
// and `value` type is `number`
interface Details {
  [key: string]: number;
}

Now that we have successfully created an index signature. We can add this as the type for the details parameter in the getDetails function.

It can be done like this,

// Index signature
// where `key` type is `string`
// and `value` type is `number`
interface Details {
  [key: string]: number;
}

// a simple function that gets
// some details of the user
function getDetails(details: Details) {
  console.log(details);
}

Let's now call the function with different objects structures and see what happens,

// Index signature
// where `key` type is `string`
// and `value` type is `number`
interface Details {
  [key: string]: number;
}

// a simple function that gets
// some details of the user
function getDetails(details: Details) {
  console.log(details);
}

// call the function
getDetails({ salary: 200000, age: 23 }); // ✅ Valid.
getDetails({ age: 23 }); // ✅ Valid.
getDetails({ name: "John Doe" }); // ❌ Error. Type 'string' is not assignable to type 'number'.

As you can see from the above code the first 2 function calls are valid because the object property's key is of string type and the value is of number type which satisfies the index signature but the third function call is invalid since the name property's value type is of string type which is not according to the index signature.

So index signature helps in enforcing the type for every key and value in an object. This is useful when we do not know the property names and their corresponding types for an object ahead of time and thus it still helps in having a type check.

See the above code live in codesandbox.

That's all 😃!

Feel free to share if you found this useful 😃.