Index Signatures in TypeScript

Learn via video courses
Topics Covered

Overview

The type system in TypeScript provides many of the same benefits, such as improved code completion, early error detection, and easier communication between components of your application. While TypeScript includes many capabilities that are recognizable to these people, it's important taking a step back to observe how JavaScript (and hence TypeScript) differs from classic OOP languages.

Introduction

When the values of the object are of consistent kinds, the type of the object or dictionary is represented by the index signature.

Objects are one of the most popular methods to deal with and transfer data around in JavaScript. In TypeScript, there is a particular type called object type that was designed just for objects. This article will teach you about Index Signatures, but first, let's define object types in TypeScript and how to operate with them.

Object Types

When working with an object, one of the first things that may come to your mind is the object type.

In JavaScript, there are two kinds of values. Primitive data types are the initial type. These are the eight fundamental data kinds, some of which you will encounter frequently.

Primitive Values

  • Boolean :
    Boolean values in JavaScript are used to represent two logical values. True or false is the only boolean type value.

  • Null :
    In Java, null is a reserved term (keyword) for literal values. It is a literal equivalent of true and false. Null, like the other keywords public, static, and final, is a keyword in Java. It is simply a value that implies that the object refers to nothing.

  • Undefined :
    A primitive value used when a variable has not been assigned a value is an undefined value.

  • Number :
    Numbers are stored in database columns as numeric data types. These data types are often classified as follows : exact numeric types, and values where accuracy and scale must be retained. INTEGER, BIGINT, DECIMAL, NUMERIC, and NUMBER are the precise numeric kinds.

  • BigInt :
    BigInts are a new JavaScript numeric primitive that can represent numbers with arbitrary precision. Bigtits allows you to securely store and operate on huge integers that are larger than the safe integer limit for Numbers.

  • String :
    A string is generally defined as a series of characters, either as a literal constant or as a variable. A string is a data type that is frequently implemented as an array data structure.

  • Symbol :
    Symbols, the most recent JavaScript primitive, add a few advantages to the language and are especially beneficial when used as object attributes.

Objects

Objects are the second type of value. By inspecting the value, you may immediately discern between a basic data type and an object in JavaScript. It is an object if the value has any attributes. It is otherwise one of the eight primordial data types. In TypeScript, each of these kinds has a matching type.

This also applies to things. There is a new type in TypeScript called object type. This type applies to any value that contains at least one property. This new type seeks to make dealing with and annotating objects easier.

Every non-primitive value, including arrays, is considered an object, and this is what the object type in TypeScript represents. Unfortunately, there's a good possibility it won't work for you.

Objects

When working with an object, one of the first things that may come to your mind is the object type.

Anonymous Object Types

TypeScript supports the definition of two types of object types. The first category is anonymous. This is when an object is defined for a specific item without the Use of a type or an interface. A function parameter is an example of an anonymous object type. Assume you have a function that takes an object as an argument.

If you want to designate the object type for this object parameter as anonymous, do so during the function definition. You specify which properties the object should have. You must also provide the kind of property value for each one.

You defined the employee object argument in the previous example. This parameter's anonymous object type indicates that the object has two properties: user_name and email. One property is of type string, the other is of any type, and both are necessary.

Named Object Types

The second method for defining object types is to use a type alias or an interface. In this scenario, one of these two is used to define the form of the item. When annotating an object with this shape, you use the type alias or interface. TypeScript will infer types for all object attributes based on the alias or interface.

The structure of the object type is the same. There are still two string-type attributes. The distinction is that the object type is now specified regardless of the function or location where it is used.

Reusability of Named and Anonymous Objects

The reusability of your code is a significant advantage of named object types. When object types are defined as named, you can use them as many times as you desire. If you export them, you may use them wherever you want. Once written, it may be used anywhere, at any time. This is not possible with unnamed types.

Because the anonymous object type has no name, it cannot be referenced anywhere in your code. If you wish to utilize the shape it specifies again, you must write it again. This is one of the reasons. TypeScript developers prefer named object types over anonymous object types. This is not to say that you should never utilize the anonymous object type.

A decent rule of thumb is to consider the thing and how likely it is that you will use its shape again. If you are likely to work with its form or anything similar, it may be a good idea to develop a type alias or an interface. The alias or interface will then be referenced anytime you work with that specific shape.

This will make making modifications as you work much easier. You will only need to alter one thing: the alias or the interface. Once you modify it, it will be applied everywhere the alias or interface is used. Consider looking for and updating all occurrences of that exact shape in your code.

This will also help you in reducing the likelihood of bugs. When you modify the alias or interface, TypeScript will instantly alert you if you need to edit any code to reflect the new shape. This will not happen with anonymous object types since TypeScript cannot employ a single source of truth.

If you are unlikely to interact with it or a similar form again, an anonymous object type will suffice.

Modifiers for Object Type and Property

All attributes are needed and configurable when you define an object type, whether anonymous or named. You may alter this with property modifiers in TypeScript.

Object characteristics that are optional :

There is a distinction between an item that may or may not have certain attributes and an object that must have certain properties. When you define an object type, TypeScript expects to discover those attributes in the object you marked with that object type.

TypeScript will complain if you fail to declare all of these attributes in the object. TypeScript will also expect to locate just the attributes you specified. It will not accept anything less. It will complain if it discovers any new attributes. There are two alternatives.

The first approach is to construct many versions of the object type to address different use cases. When you change the form of the item, this may work in some circumstances. Creating a new version only to make one attribute optional, on the other hand, is absurd. Instead, you may inform TypeScript that some property is optional.

This also informs TypeScript that the property might not be specified every time. If it is not defined, it should complain about it. Unless, of course, you try to use the property. You may accomplish this by inserting a question mark symbol (?) directly after the property name in the object type.

Readonly Project Properties :

The read-only property modifier is the second. This modification allows you to define which properties' values should not be changed once they have been initialized. It should be noted that this feature/update is only available in TypeScript. If you mark a property as read-only and then attempt to alter it, TypeScript will protest and throw an error.

This, however, will not prevent JavaScript from carrying out the update. There is no such method as a readonly property in JavaScript, at least not yet. You may make a property read-only by using the readonly keyword just before the property in the object type.

Index Signatures

In JavaScript (and hence TypeScript), an Object may be retrieved using a string to store a reference to any other JavaScript object.

Here's a simple example :

Output :

We keep a string called World under the key Case. Because we stated that it might store any JavaScript object, let's save a class instance from demonstrating the concept :

Output :

Remember how we stated it could be accessed using a string? When you supply another object to the index signature, the JavaScript runtime calls.toString on it before returning the result. As an example, consider the following :

Output :

It should be noted that toString is called anytime the obj is used in an index position.

Arrays differ slightly from one another. JavaScript VMs will strive to optimize number indexing (depending on things like whether it is an array or do the structures of items stored match etc.). As a result, a number should be regarded as a legitimate object accessor in its own right (distinct from the string). Here's an example of a basic array.

Output :

That is JavaScript. Let's have a look and see how TypeScript handles this topic gracefully.

TypeScript Index Signature

To begin, because JavaScript implicitly uses the function toString() on any object index signature, TypeScript will generate the following Error to protect newbies from shooting themselves in the foot

The justification for requiring the user to be specific is that the default function toString() implementation on an object is quite bad. For example, in version 8, it always produces [object Object] :

The number is, of course, supported because

  • It is required for good Array / Tuple support.
  • Even if you're using it for an object, the default function toString() implementation is useful (not [object Object]).

Index signatures in TypeScript must be either string or numeric.

TypeScript recognizes and supports symbols.

Declaring an Index Signature

So we've been telling TypeScript to let us do anything we want. We can explicitly provide an index signature. For example, suppose you wish to ensure that anything placed in an object using a string complies with the structure {message: string}. This is possible using the declaration {[index:string]: {message:string}}. As an example, consider the following :

The name of the index signature, for example, index in { [index:string]: { message:string}} For example, if it's usernames, you may do { [username:string]: {message:string}} to assist the next developer who will look at the code (which just might happen to be you).

Of course, number indexes are available as well, for example, [count: number]: Some other type you want to store eg Rebate

The String Index Signature Must Be Matched by All Members

When a string index signature is defined, all explicit members must also comply with that index signature. This is illustrated below :

This is done to ensure that any string access yields the same result :

Using a Small Number of String Literals

Using Mapped Types, an index signature might demand that index strings be members of a union of literal strings, for example :

This is frequently used in conjunction with the key of typeof to capture vocabulary kinds, as indicated below.

The vocabulary specification can be delayed generically :

Having Both String and Number Indexes

Although this is not a popular use case, the TypeScript compiler does support it.

The string indexer, on the other hand, is more stringent than the number indexer. This is done on purpose, for example, to allow you to type something like :

Design Pattern: Nested Index Signature

APIs that misuse string indexers are rather widespread in the JS community. A popular pattern in CSS in JS libraries

When adding index signatures, take API into account.

For example :

Avoid combining string indexers with valid values in this manner. For example, a misspelling in the padding will go unnoticed :

Instead, divide the nesting into its own property, such as nest (or children or subnodes, etc.) :

Excluding Certain Properties from the Index Signature

It is sometimes necessary to mix attributes in the index signature. This is not recommended. Instead, use the Nested index signature scheme discussed above.

If you are modeling existing JavaScript, you may get around this by using an intersection type. The following is an example of an error you may encounter if you do not use an intersection :

Here's how to use an intersection type as a workaround :

It should be noted that, while you may define it to mimic existing JavaScript, you cannot build such an object using TypeScript :

Index Signature Caveats

There are a few things you should know about TypeScript index signatures.

Non-existing Properties

What if you tried to access a non-existent property of an object with the index signature{[key: string]: string}?

As predicted, TypeScript infers the value's type to be a string. However, when you look at the runtime value, it is undefined :

According to TypeScript, the cost variable has a string type, however, its runtime value is unknown.

The index signature does nothing more than translate a key type to a value type. If the mapping is not right, the value type may differ from the actual runtime data type.

Mark the indexed value as string or undefined to improve typing accuracy. TypeScript becomes aware that the properties you are attempting to access may not exist :

In the second line of the code 'string | undefined' because the attribute may be absent.

String and Number Key

Assume you have a dictionary of number names :

Using a string key to access a value works as expected :

Is it an error if you try to access a value with the number 10 ?

this value is a string

When integers are used as keys in property accessors, JavaScript implicitly converts them to strings (names[10] is the same as names['10']). This coercion is also performed by TypeScript.

You could suppose that [key: string] and [key: string | number] is the same thing.

Conclusion

  • Except for null and undefined, primitives provide a wealth of useful functions.
  • Objects are an essential component of JavaScript. TypeScript object types may also be used to make object type-safe. Object types may also make working with objects easier in general.
  • This article taught you about anonymous and named object types in TypeScript, as well as how to utilize property modifiers and index signatures.
  • The index signature can be used to define the type of object whose values are of consistent kinds or whose structure you do not know.
  • The index signature is made up of the index name and types enclosed in square brackets and then followed by a colon and the value type: {[indexName: KeyType]: ValueType}.
  • ValueType can be any type, while KeyType can be a string, numeric, or symbol.