Skip to main content
Dictionaries in TypeScript -  How to Ensure Type Safety
21 min read

Dictionaries in TypeScript - How to Ensure Type Safety

Introduction

Dictionaries are one of the crucial data structures in programming. Dictionaries or maps are useful for describing key-value paired objects in an application. They can be implemented using Object and Map instances in JavaScript. In TypeScript, dictionaries are also implemented as a Record<> with related types for its keys and values.

TypeScript necessitates proper type annotation to a dictionary's keys and values. So, in this post, we explore how type safe dictionaries in TypeScript are implemented with necessary type definitions, annotations and specificity.

We first make sense of what dictionaries are and go through a couple of examples which illustrate that dictionaries are collections of entries with key-value pairs which adhere to a specific structure describable by their types. Through these examples, we observe how dictionaries are implemented in JavaScript with Object instances.

We then relate how dictionaries in TypeScript demand proper type definition and annotation in order to achieve type safety and specificity and see how to apply proper types for dictionary keys and entries. We elaborate with examples how to implement open ended TypeScript dictionaries with index signatures and closed set dictionaries with TypeScript generic mapped types.

Towards the later half, we cover type safe approaches to iterating over TS Object based dictionaries with for...in and for...of loops. We consider dictionary data extracted with Object.keys(), Object.values() and Object.entries, and traversing them using Array iterators like Array.prototype.map() as an example. We also encounter a TypeScript 7503 type incompatibility error while iterating over dictionary keys returned by Object.keys() with a for...in loop, and address how to solve it with the type assertion as operator.

Towards the end, we explore examples of implementing the same dictionary (fictionalDictators) features with the TypeScript Record<> utility and TS Map instances.

Prerequisites

In order to properly follow this post and test out the examples, you need to have a JavaScript engine. The discussions in this post cover intermediate-to-advanced topics in TypeScript. We expect you come with enough knowledge about at the basics of TypeScript utility types and mapped types. It'll be useful if you have worked significantly with the JavaScript Object and Map APIs as well.

TypeScript Setup

Your JavaScript engine has to have TypeScript installed. It could be Node.js in your local machine with TypeScript supported or you could use the TypeScript Playground.

What is TS Dictionary ?

A dictionary is a data structure which contains entries with unique keys that describe its content. Each key typically represents and maps to a value of known or generalizable type.

Most programming languages implement a dictionary in the form of an object with properties that are expressed with a key and its value. A trivial example of dictionary in JavaScript looks like this:

const dictators = {
ah_aladeen: "Admiral Haffaz Aladeen",
bg_brother: "Big Brother",
cr_snow: "Coriolanus Snow",
es_palpatine: "Emperor Sheev Palpatine",
};

The term "dictionary" belongs mainly to Python. It is synonymous with the hash or hash map data structure in Ruby and hash tables in Java. A dictionary is also often referred to as a map.

How Dictionaries are Implemented in JavaScript

JavaScript has a legacy of implementing dictionaries with the help of basic objects -- which are instances of the Object prototype. ES2015 brings a dedicated JavaScript Map prototype which helps implement dictionaries in JavaScript using key-value tuples in a multidimensional Array instance.

In this section, we briefly look at how object based dictionaries are implemented in JavaScript.

JavaScript Dictionaries with Object Literals

In the above dictators example, we are using a JavaScript object literal to define a list of dictators. The property keys describe a dictator's lowercase name / initials. We have the values of each key as string literals. Dictionaries are what they are called because all their values are describable by some type, such as a string. In other words, the values of a dictionary are always some consistently structured object.

For example, the dictators dictionary above could also have entries with consistently shaped Object values, like this:

const dictators = {
ah_aladeen: {
name: "Admiral Haffaz Aladeen",
dominion: "North African Republic of Wadiya",
},
bg_brother: {
name: "Big Brother",
dominion: "Oceania",
},
cr_snow: {
name: "Coriolanus Snow",
dominion: "The Capitol of Anem",
},
es_palpatine: {
name: "Emperor Sheev Palpatine",
dominion: "Star Wars Galaxy, First Order",
},
};

This precisely means that in a dictionary, the items are composed in a well defined shape - rather than assuming random shapes from inconsistent property names and values.

JavaScript Dictionaries with Object Prototype

In JavaScript, we can create a dictionary using the Object constructor and then add items:

const dictators = new Object();

dictators["ah_aladeen"] = "Admiral Haffaz Aladeen";
dictators.bg_brother = "Big Brother";
console.log(dictators); // Object { ah_aladeen: "Admiral Haffaz Aladeen", bg_brother: "Big Brother" }

A different approach to creating dictionaries from a prototype is using the Object.create() method:

const dictators_dictionary = { title: "The Ultimate Dictionary of Dictators" };

const dictators = Object.create(dictators_dictionary);

dictators["ah_aladeen"] = "Admiral Haffaz Aladeen";
dictators.bg_brother = "Big Brother";

console.log(dictators); // Object { ah_aladeen: "Admiral Haffaz Aladeen", bg_brother: "Big Brother" }
console.log(dictators.title); // "The Ultimate Dictionary of Dictators"

Dictionaries created from a prototype give access to inherited properties. You can see this in the title field of the above dictators object, which is derived from the dictators_dictionary prototype.

TIP

Overall, it is important that a JavaScript object based dictionary maintains consistent structure in its item values.

Dictionaries in TypeScript

In TypeScript, the structure of a dictionary is described by respective types for its keys and values. Type annotation of dictionary entries becomes mandatory in TypeScript in order to achieve type safety and precise type specificity. Otherwise, we are subject to TypeScript compiler errors and brittle code that is dangerous for maintainability and scalability.

Why Dictionary Type Safety is Needed in TypeScript

Since TypeScript applies a structural typing system, randomly shaped objects pose a challenge while implementing dictionaries in TypeScript. Structurally inconsistent values of dictionary key and values throw type errors in TypeScript.

One common example of dictionary type errors, as discussed later in a later section, is while iterating over a dictionary via its keys extracted in an array with Object.keys().

Also, TypeScript's structural typing complements the consistency of structure sought in dictionary entries. So, it is essential that we annotate well-defined types to dictionary keys and values in TypeScript.

Dictionaries in TypeScript: How to Apply Type Safety

Implementing type-safe dictionaries in TypeScript involves annotating types to both the keys and the values of the items. Additionally, we can also apply type specificity of dictionary keys and values.

While some dictionaries may have less constrained types -- for example, as implemented with index signature -- others can achieve accurate specificity by imposing type constraints with union of keys, the in narrowing operator and the as type assertion operator.

TypeSafe Dictionaries in TypeScript: Object Examples

In this section, we cover some of the common examples of applying type safety to Object based dictionaries in TypeScript.

TypeScript Dictionaries with Object Index Signatures

We can implement a open ended dictionary in TypeScript with index signatures. For example, the first dictators dictionary with a string value we considered in this post can be annotated as below:

type TDictators = {
[dict_key: string]: string;
};

const dictators: TDictators = {
ah_aladeen: "Admiral Haffaz Aladeen",
bg_brother: "Big Brother",
cr_snow: "Coriolanus Snow",
es_palpatine: "Emperor Sheev Palpatine",
};

console.log(dictators.ah_aladeen); // "Admiral Haffaz Aladeen"

Here, we first defined a TDictators type with index signature syntax and then annotated the dictators object with TDictators.

Notice, we have to annotate both the key and value of the dictionary items. Notice also that this dictionary is a perpetually extendable one. That's because, with the index signature type above, we have annotated the keys to be any string. And we want the value to be string as well.

The value of a TypeScript dictionary entry can be any valid type, in order to reflect a consistent structure demanded by dictionary entries. For example, it can be an object of the following shape:

type TDictatorInfo = {
name: string;
dominion: string;
};

And this allows us to express the dictators dictionary values with an object that conforms to this shape:

type TDictatorInfo = {
name: string;
dominion: string;
};

type TDictators = {
[dict_key: string]: TDictatorInfo;
};

const dictators: TDictators = {
ah_aladeen: {
name: "Admiral Haffaz Aladeen",
dominion: "North African Republic of Wadiya",
},
bg_brother: {
name: "Big Brother",
dominion: "Oceania",
},
cr_snow: {
name: "Coriolanus Snow",
dominion: "The Capitol of Anem",
},
es_palpatine: {
name: "Emperor Sheev Palpatine",
dominion: "Star Wars Galaxy, First Order",
},
};

dictators["sl_snoke"] = {
name: "Supreme Leader Snoke",
dominion: "Star Wars Galaxy, First Order",
};

Notice, with the above definition / annotation changes, the dictators dictionary remains open ended since keys can still be any string. So, we are able to keep adding entries, as we are doing with the dictators["sl_snoke"] = assignment.

However, if we try to add a value inconsistent with TDictatorInfo, we get TypeScript errors:

dictators["sl_kylo"] = "Supreme Leader Kylo Ern"; // Type 'string' is not assignable to type 'TDictatorInfo'.(2322)
dictators["sl_kylo"] = {
name: "Supreme Leader Kylo Ern",
nick_name: "Ben Solo",
}; // Object literal may only specify known properties, and 'nick_name' does not exist in type 'TDictatorInfo'.(2353)

Here, when we try to assign a string value to the item, TypeScript throws 2322 error. And when we add an inconsistent property with unknown / missing type, we get a 2353 error.

So, as we can already see, proper type annotation of dictionaries proves useful for the stability of the codebase.

TIP

Take note that TS dictionaries with index signatures are open ended and extendable, as we can add as many items to the dictionary as we want.

Reusable TypeScript Dictionaries with Generic Mapped Types

We can limit the keys of a dictionary with union of keys. In such cases, we need to derive a generic mapped type to describe the dictionary by narrowing down a list of keys with TypeScript's in operator. For example:

type TDictionary<Keys extends string | number | symbol, Value> = {
[dict_key in Keys]: Value;
};

The TDictionary type now is a generic type that accepts Keys and Value as type parameters. TDictionary is also a mapped type that limits its keys to the members of Keys union type. Keys is a union type because it is being looped over by the in operator. And we want Keys to be only among string, number and symbol primitive types. So, we are applying type specificity on several levels to consolidate type safety besides code reusability.

Now we can use this type to constrain the dictionary entries to a certain set of keys:

type TDictionary<Keys extends string | number | symbol, Value> = {
[dict_key in Keys]: Value;
};

type TFictionalDictatorKeys =
| "ah_aladeen"
| "bg_brother"
| "cr_snow"
| "es_palpatine";

type TDictatorInfo = {
name: string;
dominion: string;
};

type TFictionalDictators = TDictionary<TFictionalDictatorKeys, TDictatorInfo>;

const fictionalDictators: Partial<TFictionalDictators> = new Object();

fictionalDictators["ah_aladeen"] = {
name: "Admiral Haffaz Aladeen",
dominion: "North African Republic of Wadiya",
}; // Does okay. No error.

fictionalDictators["sl_snoke"] = {
name: "Supreme Leader Snoke",
dominion: "Star Wars Galaxy, First Order",
}; // Throws error: Property 'sl_snoke' does not exist on type 'Partial<TFictionalDictators>'.(7053)

Here, we limit the keys of the TFictionalDictators type with the TFictionalDictatorKeys union type and set the type for the value to TDictatorInfo type. We are also partializing the type for fictionalDictators dictionary object with the Partial<> transformation utility. Partializing allows us to instantiate fictionalDictators without initializing any property -- as with no object passed to new Object().

Notice, an entry with acceptable key ("ah_aladeen") encounters no complain. However, when we try to add a non-member key ("sl_snoke"), TypeScript throws 7053 error.

Since TDictionary<> is now a generic type, we can reuse it for a dictionary with Values that are string:

type TFictionalDictators = TDictionary<TFictionalDictatorKeys, string>;

const fictionalDictators: Partial<TFictionalDictators> = new Object();

fictionalDictators["ah_aladeen"] = "Admiral Haffaz Aladeen";
console.log(fictionalDictators); // Object { ah_aladeen: "Admiral Haffaz Aladeen" }

Type Safe Dictionaries in TypeScript: A Record<> Example

The reusable dictionary TDictionary<> type that we have defined implements the features of TypeScript's Record<> utility type. Similar to our implementation in TDictionary<Keys, Value>, Record<> is a generic type that accepts the keys and value types as type parameters to map a set of keys to a value that represents a data entity or record.

TIP

Although TypeScript Record<> is often used to represent and manipulate database record types, we can use it to annotate TS Object based dictionaries as well.

Below is the similar annotation of the fictionalDictators dictionary with TypeScript's Record<> utility:

type TFictionalDictatorKeys =
| "ah_aladeen"
| "bg_brother"
| "cr_snow"
| "es_palpatine";

type TDictatorInfo = {
name: string;
dominion: string;
};

type TFictionalDictators = Record<TFictionalDictatorKeys, TDictatorInfo>;

const fictionalDictators: Partial<TFictionalDictators> = {};

fictionalDictators["ah_aladeen"] = {
name: "Admiral Haffaz Aladeen",
dominion: "North African Republic of Wadiya",
}; // Does okay. No error.

fictionalDictators["sl_snoke"] = {
name: "Supreme Leader Snoke",
dominion: "Star Wars Galaxy, First Order",
}; // Same 7053 error due to union member constraint

Iterating Over TypeScript Dictionaries

Type safety and type specificity of TypeScript dictionaries prove important and offer convenience while iterating over a dictionary. This is particularly because individual keys or values or entries have to be consistently typed for the iteration operations.

Most often, as in the examples we have explored so far, the types of the keys and values need to be in the form of predefined, and reusable type definitions so that all can be used as a suite accompanies with the main dictionary type:

type TDictionary<Keys extends string | number | symbol, Value> = {
[dict_key in Keys]: Value;
};

type TFictionalDictatorKeys =
| "ah_aladeen"
| "bg_brother"
| "cr_snow"
| "es_palpatine";

type TDictatorInfo = {
name: string;
dominion: string;
};

type TFictionalDictators = TDictionary<TFictionalDictatorKeys, TDictatorInfo>;

const fictionalDictators: Partial<TFictionalDictators> = {};

There are various use cases and hence various ways where a dictionary is iterated over. Most legacy code involve iterating over a dictionary with the keys extracted in an array with Object.keys() method. Similar extraction of dictionary entries with Object.entries() and only values with Object.values() are also often used.

For iteration, we can use the for...in loop directly on the dictionary object itself or the for...of loop on the extracted arrays. We can also use regular JS iterators like Array.prototype.map(), Array.prototype.forEach(), Array.prototype.reduce() and the likes to match our use cases.

In the sections that follow, we discuss with examples the type safety aspects of some of these iteration APIs.

For these examples, we use the following types and fictionalDictators dictionary object:

Show example dictionary object and types
type TDictionary<Keys extends string | number | symbol, Value> = {
[dict_key in Keys]: Value;
};

type TFictionalDictatorKeys =
| "ah_aladeen"
| "bg_brother"
| "cr_snow"
| "es_palpatine";

type TDictatorInfo = {
name: string;
dominion: string;
};

type TFictionalDictators = TDictionary<TFictionalDictatorKeys, TDictatorInfo>;

const fictionalDictators: TFictionalDictators = {
ah_aladeen: {
name: "Admiral Haffaz Aladeen",
dominion: "North African Republic of Wadiya",
},
bg_brother: {
name: "Big Brother",
dominion: "Oceania",
},
cr_snow: {
name: "Coriolanus Snow",
dominion: "The Capitol of Anem",
},
es_palpatine: {
name: "Emperor Sheev Palpatine",
dominion: "Star Wars Galaxy, First Order",
},
};

Iterating Over TypeScript Object Dictionaries with for...in Loop

We have a fictionalDictators object and we can iterate over it directly with the for...in loop. The for...in loop gives access to the keys of the dictionary with which we can attempt to access the entries.

For example, when we want to store the fictional dictator names in an array:

const fictionalDictatorNames = [];

for (const key in fictionalDictators) {
fictionalDictatorNames.push(fictionalDictators[key]?.name); // Gives 7053 error
}

console.log(fictionalDictatorNames);

With the above code, we try to populate the fictionalDictatorNames array with the names of the dictators from the fictionalDicatiors dictionary.

However, we encounter this big error:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'TFictionalDictators'.
No index signature with a parameter of type 'string' was found on type 'TFictionalDictators'.(7053)

We'll come to what it stands for and where it originates from in a later section when we encounter it again with Object.keys(), but we can fix this with type assertion using the TypeScript as operator:

const fictionalDictatorNames = [];

for (const key in fictionalDictators) {
fictionalDictatorNames.push(
fictionalDictators[key as TFictionalDictatorKeys]?.name,
);
}

console.log(fictionalDictatorNames); // ["Admiral Haffaz Aladeen", "Big Brother", "Coriolanus Snow", "Emperor Sheev Palpatine"]

And this time, we get the desired array of dictator names without error.

Iterating Over TypeScript Dictionaries with for...of Loop

The for...of loop can be used to iterate over array of keys, values and key-value tuples.

For example, we can get the same fictionalDictatorNames array in the previous example by extracting the keys first in a separate array with Object.keys(). And then by iterating over the array with for...of loop to populate fictionalDictatorNames:

const dictatorKeysArray = Object.keys(fictionalDictators);
const fictionalDictatorNames = [];

for (const key of dictatorKeysArray) {
fictionalDictatorNames.push(
fictionalDictators[key as TFictionalDictatorKeys]?.name,
);
}

console.log(fictionalDictatorNames); // ["Admiral Haffaz Aladeen", "Big Brother", "Coriolanus Snow", "Emperor Sheev Palpatine"]

Notice this time too, we need to apply type assertion with the as operator. Otherwise, we get the same 7053 error.

Let's cover a couple of more examples of using the for...of loop. In the code snippet below, we iterate over the dictionary values and also the key-value entries themselves:

const dictatorsInfoArray = Object.values(fictionalDictators);
const fictionalDictatorNames = [];

for (const value of dictatorsInfoArray) {
fictionalDictatorNames.push(value?.name);
}
console.log(fictionalDictatorNames); // ["Admiral Haffaz Aladeen", "Big Brother", "Coriolanus Snow", "Emperor Sheev Palpatine"]

const dictatorItemsArray = Object.entries(fictionalDictators);
const fictionalDictatorList = [];

for (const entry of dictatorItemsArray) {
fictionalDictatorList.push(entry[1]);
}

console.log(fictionalDictatorList);

In the first part that involves the values extracted with Object.values(), we iterated over the dictatorsInfoArray array and used each value to populate the fictionalDictatorNames like before, but this time without utilizing the keys.

In the second part, we extracted the dictionary entries with Object.entries() which gives an array of key-value tuples. We then iterate over the array and populate the fictionalDictatorList array with the dictionary entry values.

Notice, in both instances, we didn't need to apply any type assertion like we had to with Object.keys(). The respective types for the values and entries are assigned by TypeScript's intelligent type inference.

The fictionalDictatorList ends up being an array of TDictatorInfo objects.

Show `fictionalDictatorList` array
[
{
name: "Admiral Haffaz Aladeen",
dominion: "North African Republic of Wadiya",
},
{
name: "Big Brother",
dominion: "Oceania",
},
{
name: "Coriolanus Snow",
dominion: "The Capitol of Anem",
},
{
name: "Emperor Sheev Palpatine",
dominion: "Star Wars Galaxy, First Order",
},
];

The original dictatorItemsArray entries generated by Object.entries(fictionalDictators) is an array of dictionary key-value tuples.

Show `dictatorItemsArray`
[
[
"ah_aladeen",
{
name: "Admiral Haffaz Aladeen",
dominion: "North African Republic of Wadiya",
},
],
[
"bg_brother",
{
name: "Big Brother",
dominion: "Oceania",
},
],
[
"cr_snow",
{
name: "Coriolanus Snow",
dominion: "The Capitol of Anem",
},
],
[
"es_palpatine",
{
name: "Emperor Sheev Palpatine",
dominion: "Star Wars Galaxy, First Order",
},
],
];

The inferred type of this object has a shape similar to the Map prototype and it looks as below:

type TDictatorItemsArray: [string, TDictatorInfo][]

We cover type-safe TypeScript Map based dictionaries in this later section.

Iterating Over TypeScript Dictionaries with Array Methods

It is common to use Array iterators such as Array.prototype.map(), Array.prototype.reduce(), Array.prototype.forEach() to traverse through arrays generated by Object.keys(), Object.values() and Object.entries(). These iterators operate in similar ways with O(n) complexity, so we'll just cover an example of Array.prototype.map().

For instance, we want to map fictionalDictators to only its dominions based on the keys extracted with Object.keys():

const dictatorKeysArray = Object.keys(fictionalDictators);
const fictionalDictatorships = dictatorKeysArray?.map(
(key) => fictionalDictators[key]?.dominion,
);
console.log(fictionalDictatorships);

Here also, we run into the same 7053 error we encountered before with object keys:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'TFictionalDictators'.
No index signature with a parameter of type 'string' was found on type 'TFictionalDictators'.(7053)

How to Solve TypeScript Mapped Type Keys Problem: Type Assertion with as

Basically, the 7503 error happens when we use the keys to iterate over a dictionary when it is an object represented by a TypeScript mapped type. Apparently, the error above points to incompatibility of the string type of the items in the array extracted by Object.keys() to the union keys, TFictionalDictatorKeys, in the TDictionary mapped type.

Even if we try to annotate the keys directly with string[] or TFictionalDictatorKeys they are still incompatible. In other words, these remain invalid:

const dictatorKeysArray: string[] = Object.keys(fictionalDictators); // Gives same 7503 error

// OR
const dictatorKeysArray: TFictionalDictatorKeys =
Object.keys(fictionalDictators); // Gives 2322 error

Therefore, we have to assert the key to TFictionalDictatorKeys with the help of the as type assertion operator:

const dictatorKeysArray = Object.keys(fictionalDictators);
const fictionalDictatorships = dictatorKeysArray?.map(
(key) => fictionalDictators[key as TFictionalDictatorKeys]?.dominion,
);
console.log(fictionalDictatorships); // ["North African Republic of Wadiya", "Oceania", "The Capitol of Anem", "Star Wars Galaxy, First Order"]

TypeScript Dictionaries with the Map Prototype

TypeScript dictionaries can also be implemented with the ES2015 Map prototype. The ES2015 Map data structure is an array of key-value tuples. TypeScript offers native support for Map data structure as a generic that accepts types for the keys and entry values as type parameters.

Closed Set Dictionaries with TypeScript Map

We can express TypeScript dictionaries with new Map<Keys, Value>, as we did with the custom generic types such as TDictionary<> and the Record<> utility.

For example, we can re-write the fictionaDictators dictionary with the TFictionalDictatorKeys keyset using a Map:

type TDictionary<Keys extends string | number | symbol, Value> = {
[dict_key in Keys]: Value;
};

type TFictionalDictatorKeys =
| "ah_aladeen"
| "bg_brother"
| "cr_snow"
| "es_palpatine";

type TDictatorInfo = {
name: string;
dominion: string;
};

type TFictionalDictators = Map<TFictionalDictatorKeys, TDictatorInfo>;

const fictionalDictators: TFictionalDictators = new Map([
[
"ah_aladeen",
{
name: "Admiral Haffaz Aladeen",
dominion: "North African Republic of Wadiya",
},
],
[
"bg_brother",
{
name: "Big Brother",
dominion: "Oceania",
},
],
]);

fictionalDictators.set("cr_snow", {
name: "Coriolanus Snow",
dominion: "The Capitol of Anem",
}); // Works okay. No error

console.log(fictionalDictators); // Map(3)

fictionalDictators.set("sl_snoke", {
name: "Supreme Leader Snoke",
dominion: "Star Wars Galaxy, First Order",
}); // Argument of type '"sl_snoke"' is not assignable to parameter of type 'TFictionalDictatorKeys'.(2345)

You can notice, this time, we are using the TypeScript type supported Map class. And we can use it with the same type parameters for Keys and Values as we did before with the Object examples.

Notice that we are now passing a array of tuples to the new Map initializer. This is similar to the array of tuples we get from Object.entries() in the TDictionary<> example with Object instance. Take note also that we are not able to enter invalid keys ("sl_snoke") when we limit them to a union with TFictionalDictatorKeys.

Open Ended Dictionaries with TypeScript Map

When we want the Map based dictionary to be extendable, we can just make the keys to be more generous strings:

type TFictionalDictators = Map<string, TDictatorInfo>;

const fictionalDictators: TFictionalDictators = new Map();

fictionalDictators.set("cr_snow", {
name: "Coriolanus Snow",
dominion: "The Capitol of Anem",
});

fictionalDictators.set("sl_kyloren", {
name: "Supreme Leader Kylo Ern",
dominion: "Star Wars Galaxy, First Order",
});

console.log(fictionalDictators); // Map(2)

Summary

In this post, we covered in significant depth how to implement properly defined and annotated dictionaries in TypeScript. We illustrated common approaches of applying type safety to TypeScript dictionaries defined with Object and Map APIs, the Record<> utility type, the for...in and for...of loops as well as the Array.prototype.map() method.

We stressed that dictionaries are composed of key-value pairs that are described by respective types in TypeScript. We acknowledged how proper type definition and annotation in Object based dictionaries lead to consistent and type safe code that offer convenient type inference later during iteration. As part of the examples, we implemented the same fictionalDictators dictionary in three different ways: first with a custom typed TDictionary<> utility, then using the TypeScript Record<> utility type and finally with the TypeScript type supported Map<> instance.

While exploring the examples, we encountered a type incompaitbility problem (TS 7503 error) of dictionary keys implemented with a TypeScript mapped type. And we learned how to resolve it by type assertion with the as operator.