Building a type-safe dictionary in TypeScript

A dictionary is a common data structure in a programming language. As JavaScript developers, it’s important to work with type-safe dictionaries because there will be conflicts or unexpected issues if we store different types of data within the same dictionary.

In this article, we’ll cover how to build a type-safe dictionary using TypeScript.

What is a type-safe dictionary?

First of all, we need to know what a dictionary is. In a programming language, a dictionary is a typical data structure that stores data in key-value pairs. JavaScript, however, does not offer a Dictionary type. Thankfully, we can create a type-safe dictionary in a few simple ways.

Using Object types in JavaScript

There are two primary ways to create a dictionary in JavaScript: using the Object type and using key-value pairs. The most popular implementation in JavaScript is to create one with the Object type. Let’s create a dictionary using the following code:

// Using the built-in Object
let dictionaryViaObject = new Object();

// Using the Object literal notation
let dictionaryViaLiteral = {};

We can create a dictionary with the initial data as key-value pairs:

let dictionaryViaLiteral = {
  'firstName': 'Gapur',
  'lastName': 'Kassym',
  'country': 'Kazakhstan'
};

We created the dictionaryViaLiteral dictionary with the key and value as string types.

If you want to change or add the value to the dictionary, you can set the new value by calling on the dictionary key, like so:

// Using bracket
dictionaryViaLiteral['firstName'] = 'New Name';

// Using directly by property name via dot
dictionaryViaLiteral.firstName = 'Tom';

We can access the value from the dictionary by directly calling the property name or indexer:

// Using bracket/indexer
const firstName = dictionaryViaLiteral['firstName'];

// Using directly by property name via dot
const firstName = dictionaryViaLiteral.firstName;

Using Map in JavaScript

A Map is a collection of key-value pairs, just like an object. The main difference is that Map allows you to use keys and values of any type. The Map provides amazing instance methods to manipulate with a dictionary. If you are interested, you can read more here.

// Using Map
const dictionaryViaMap = new Map();

// Add value using string key
dictionaryViaMap.set("1", "string1");

// Add value using number key
dictionaryViaMap.set(1, "number1");     

// Add value using boolean key
dictionaryViaMap.set(true, "boolean1");

The Map stores value by any type of key, and thus, they return the two different values:

// Return string1
const string1 = dictionaryViaMap.get('1');

// Return number1
const number1 = dictionaryViaMap.get(1);

In order to update values in the Map dictionary, we should call set method by key:

// Update the value
dictionaryViaMap.set('1', 'updatedString1');

Dictionary type errors in TypeScript

When we use the dictionary in TypeScript after previously using it in JavaScript, we’ll run into errors because TypeScript needs to know the data type of an object before it can be accessed.

This means we will not have problems with the following code in JavaScript, but we will have problems with it in TypeScript. Let’s take a look.

const dictionary = {};
dictionary.firstName; // Property 'firstName' does not exist on type '{}'

Here, dictionary.lastName returns undefined in JavaScript, but in TypeScript, it will throw an error.

const dictionary = { firstName: 'Gapur' };
// Return the firstName Gapur
dictionary.firstName;

// Property 'lastName' does not exist on type '{ firstName: string; }'
dictionary.lastName;

Sure, we can use type any in our code, but why use TypeScript without type checking?

const dictionary: any = {};
dictionary.firstName = 'Gapur'; // It works
dictionary.lastName = 'Kassym'; // It works

Building a type-safe dictionary in TypeScript

There are three ways to avoid type issues in TypeScript.

1. Using indexed object notation

We can check the type of data by using indexed object notation. Let’s create the dictionary with key and value as string types:

const dictionary: { [key: string]: string } = {};
dictionary.firstName = 'Gapur'; // It works very well
dictionary.lastName = true; // Type 'boolean' is not assignable to type 'string'

We can call key name whatever we want. For this example, I would like to name it key.

Also, we can’t leave out the key name or use union types, according to the syntax rule.

// 'string' only refers to a type, but is being used as a value here.
const dictionaryWithoutKeyName: { [string]: string } = {}; // Error
dictionary.firstName = 'Gapur';

// An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead
const dictionaryWithUnionType: { [key: 'firstName' | 'lastName']: string } = {}; // Error
dictionary.firstName = 'Tom';

Let’s complicate our example:

type User = {
    firstName: string;
    lastName: string;
}
const dictionary: { [key: number]: User } = {};
// Create user with firstName and lastName
dictionary[1] = {
    firstName: 'Gapur',
    lastName: 'Kassym'
};

// We can't add location property because User type doens't exist in the location
dictionary[2] = {
    firstName: 'Tom',
    lastName: 'Jones',
    location: 'London', // Type '{ firstName: string; lastName: string; location: string; }' is not assignable to type 'User'.
}

If we want to omit a property, we can use Partial utils for that.

type User = {
    firstName: string;
    lastName: string;
}
const dictionary: { [key: number]: User } = {};
// Property 'lastName' is missing in type '{ firstName: string; }' but required in type 'User'.
dictionary[1] = {
    firstName: 'Gapur',
}; // Error

const dictionaryWithPartial: { [key: number]: Partial<User> } = {};
// Works very well
dictionaryWithPartial[1] = {
    firstName: 'Tom'
}

2. Using the Record<Keys, Type> utility

The Record<Keys, Type> is a TypeScript utility for creating key-value objects. It is a great choice if you want to create a key type as unions or enums.

const dictionary: Record<string, string> = {};
dictionary.firstName = 'Gapur';
dictionary.lastName = 'Kassym';

Let’s use a union type:

type UserFields = 'firstName' | 'lastName';
let dictionaryUnion: Record<UserFields, string> = {
    firstName: 'Tom',
    lastName: 'Jones'
}; // Works very well

dictionaryUnion = {
    firstName: 'Aidana',
    lastName: 'Kassym',
    location: 'London' // Type is not assignable to type 'Record<UserFields, string>'
}; // Error

3. Using Map in TypeScript

We discussed using Map for creating type-safe dictionaries in JavaScript. Let’s build a simple dictionary with the key as a string and value as a number in TypeScript:

const dictionary = new Map<string, number>();
dictionary.set('JavaScript', 4); // No Error
dictionary.set('HTML', 4); // Works
// Argument of type 'string' is not assignable to parameter of type 'number'
dictionary.set('react', '4'); // Error

We can also use the key as the union type and the value as the object type:

type JobInfo = {
    language: string,
    workExperience: number,
}
type JobPosition = 'Frontend' | 'Backend';
const dictionary = new Map<JobPosition, JobInfo>();
dictionary.set('Frontend', {
    language: 'JavaScript',
    workExperience: 5
});
dictionary.set('Backend', {
    language: 'Python',
    workExperience: 4
});

Conclusion

How to best build a type-safe dictionary depends, as usual, on your use-case. If you want to build a simple dictionary, you can use the indexed object notation. If you work with the unions, enums, or more complex data, employing the Record<Keys, Type> util is best.

In general, using Map is a great practical way to solve type-safe issues with various types of data.

Thanks for reading. I hope you found this piece useful. Happy coding!

The post Building a type-safe dictionary in TypeScript appeared first on LogRocket Blog.

This article was republished from its original source.
Call Us: 1(800)730-2416

Pixeldust is a 20-year-old web development agency specializing in Drupal and WordPress and working with clients all over the country. With our best in class capabilities, we work with small businesses and fortune 500 companies alike. Give us a call at 1(800)730-2416 and let’s talk about your project.

FREE Drupal SEO Audit

Test your site below to see which issues need to be fixed. We will fix them and optimize your Drupal site 100% for Google and Bing. (Allow 30-60 seconds to gather data.)

Powered by

Building a type-safe dictionary in TypeScript

On-Site Drupal SEO Master Setup

We make sure your site is 100% optimized (and stays that way) for the best SEO results.

With Pixeldust On-site (or On-page) SEO we make changes to your site’s structure and performance to make it easier for search engines to see and understand your site’s content. Search engines use algorithms to rank sites by degrees of relevance. Our on-site optimization ensures your site is configured to provide information in a way that meets Google and Bing standards for optimal indexing.

This service includes:

  • Pathauto install and configuration for SEO-friendly URLs.
  • Meta Tags install and configuration with dynamic tokens for meta titles and descriptions for all content types.
  • Install and fix all issues on the SEO checklist module.
  • Install and configure XML sitemap module and submit sitemaps.
  • Install and configure Google Analytics Module.
  • Install and configure Yoast.
  • Install and configure the Advanced Aggregation module to improve performance by minifying and merging CSS and JS.
  • Install and configure Schema.org Metatag.
  • Configure robots.txt.
  • Google Search Console setup snd configuration.
  • Find & Fix H1 tags.
  • Find and fix duplicate/missing meta descriptions.
  • Find and fix duplicate title tags.
  • Improve title, meta tags, and site descriptions.
  • Optimize images for better search engine optimization. Automate where possible.
  • Find and fix the missing alt and title tag for all images. Automate where possible.
  • The project takes 1 week to complete.