- keyof and Lookup Types in TypeScript
- #The keyof Operator
- #Indexed Access Types
- Advanced TypeScript Fundamentals
- Typescript key value type
- # Table of Contents
- # Defining and using a Key-Value pair in TypeScript
- # Using a Map to define a Key-value pair in TypeScript
- # Retrieving values from the Map
- # Adding key-value pairs to the Map
- # Iterating over the Map
- # Converting the Map’s keys or values to an array
- # Getting the length of the Map
- # Deleting all key-value pairs from the Map
- # Additional Resources
keyof and Lookup Types in TypeScript
JavaScript is a highly dynamic language. It can be tricky sometimes to capture the semantics of certain operations in a static type system. Take a simple prop function, for instance:
function prop(obj, key) return objTypescript key value type; >
It accepts an object and a key and returns the value of the corresponding property. Different properties on an object can have totally different types, and we don’t even know what obj looks like.
So how could we type this function in TypeScript? Here’s a first attempt:
function prop(obj: <>, key: string) return objTypescript key value type; >
With these two type annotations in place, obj must be an object and key must be a string. We’ve now restricted the set of possible values for both parameters. The return type is still inferred to be any , however:
const todo = id: 1, text: "Buy milk", due: new Date(2016, 11, 31), >; const id = prop(todo, "id"); // any const text = prop(todo, "text"); // any const due = prop(todo, "due"); // any
Without further information, TypeScript can’t know which value will be passed for the key parameter, so it can’t infer a more specific return type for the prop function. We need to provide a little more type information to make that possible.
#The keyof Operator
Enter TypeScript 2.1 and the new keyof operator. It queries the set of keys for a given type, which is why it’s also called an index type query. Let’s assume we have defined the following Todo interface:
interface Todo id: number; text: string; due: Date; >
We can apply the keyof operator to the Todo type to get back a type representing all its property keys, which is a union of string literal types:
type TodoKeys = keyof Todo; // "id" | "text" | "due"
We could’ve also written out the union type «id» | «text» | «due» manually instead of using keyof , but that would’ve been cumbersome, error-prone, and a nightmare to maintain. Also, it would’ve been a solution specific to the Todo type rather than a generic one.
#Indexed Access Types
Equipped with keyof , we can now improve the type annotations of our prop function. We no longer want to accept arbitrary strings for the key parameter. Instead, we’ll require that the key actually exists on the type of the object that is passed in:
function propT, K extends keyof T>(obj: T, key: K) return objTypescript key value type; >
TypeScript now infers the prop function to have a return type of T[K] , a so-called indexed access type or lookup type. It represents the type of the property K of the type T . If we now access the three todo properties via the prop method, each one will have the correct type:
const todo = id: 1, text: "Buy milk", due: new Date(2016, 11, 31), >; const id = prop(todo, "id"); // number const text = prop(todo, "text"); // string const due = prop(todo, "due"); // Date
Now, what happens if we pass a key that doesn’t exist on the todo object?
The compiler complains, and that’s a good thing! It prevented us from trying to read a property that’s not there.
For another real-world example, check out how the Object.entries() method is typed in the lib.es2017.object.d.ts type declaration file that ships with the TypeScript compiler:
interface ObjectConstructor // . entriesT extends < Typescript key value type: any >, K extends keyof T>( o: T, ): [keyof T, T[K]][]; // . >
The entries method returns an array of tuples, each containing a property key and the corresponding value. There are plenty of square brackets involved in the return type, admittedly, but there’s the type safety we’ve been looking for!
This article and 44 others are part of the TypeScript Evolution series. Have a look!
Advanced TypeScript Fundamentals
A deep dive into the fundamnetals of TypeScript’s type system. Learn about the optional chaining ( ?. ) and nullish coalescing ( ?? ) operators, assertion functions, truly private class fields, conditional types, template literal types, adn more.
Typescript key value type
Last updated: Jan 21, 2023
Reading time · 5 min
# Table of Contents
# Defining and using a Key-Value pair in TypeScript
One way to define a key-value pair in TypeScript is to use an index signature.
An index signature is used when we don’t know all the names of a type’s keys ahead of time, but we know the shape of their values.
Copied!const employee: [key: string]: string | number > = >; employee.name = 'Bobby Hadz'; employee.salary = 100; // 👇️ console.log(employee);
The
The index signature in the examples means that when the object is indexed with a string , it will return a value of type string or number .
You might also see the index signature
It represents a key-value structure that when indexed with a string returns a value of any type (very broad).
Copied!const employee: [key: string]: any > = >; employee.name = 'Bobby Hadz'; employee.salary = 100; employee.years = [2023, 2024]; // 👇️ console.log(employee);
The
You can declare the types of the keys and values that you know ahead of time and use an any type for the ones you don’t.
Copied!type Employee = [key: string]: any; name: string; salary: number; >; const employee: Employee = name: 'Bobby Hadz', salary: 100, >; employee.country = 'Bobby Hadz'; employee.years = [2021, 2022];
We declared types for the name and salary properties, which we know about ahead of time and used an index signature to still allow us to assign any key with any value to the object.
This is useful because we still get type support for the name and salary properties and the type checker would throw an error if we try to set the properties to an incompatible type.
# Using a Map to define a Key-value pair in TypeScript
You can use a Map to define a key-value pair. A generic is used to set the type of the Map’s keys and values.
Copied!const map1 = new Map(); map1.set('name', 'Bobby Hadz'); map1.set('age', 30); map1.set('country', 'Germany'); // 👇️ < 'name' =>'Bobby Hadz', 'age' => 30, 'country' => 'Germany' > console.log(map1); // 👇️ Bobby Hadz console.log(map1.get('name'));
The Map in the example is implicitly typed to have keys and values of any type, but you can also explicitly specify a type.
Copied!const map1 = new Mapstring, string | number>(); map1.set('name', 'Bobby Hadz'); map1.set('age', 30); map1.set('country', 'Germany'); console.log(map1); // 👇️ Bobby Hadz console.log(map1.get('name'));
We used a generic to declare a Map that has string keys and string or number values.
The syntax that is used to type a Map is — new Map() .
For example, new Map is a Map object with keys of type number and values of type string .
All of the key-value pairs we add to the Map have to conform to the specified types, otherwise, an error is thrown.
Copied!const map1 = new Mapstring, string | number>([ ['name', 'Bobby Hadz'], ['age', 30], ]); // ⛔️ Error: Argument of type 'boolean' is not // assignable to parameter of type 'string | number'.ts(2345) map1.set('country', true);
We used a union type to set the Map’s values to be either strings or numbers, but a boolean value is not a member of the union, so we got an error.
# Retrieving values from the Map
We typed the values of the Map to be strings or numbers, so when we use the Map.get method, we will get a value of type string , number or undefined .
Copied!const map1 = new Mapstring, string | number>([ ['name', 'Bobby hadz'], ['age', 30], ['country', 'Germany'], ]); // 👇️ const country: string | number | undefined const country = map1.get('country'); if (typeof country === 'string') // 👇️ GERMANY console.log(country.toUpperCase()); >
The country variable is possibly undefined because TypeScript can’t know in advance whether the Map contains a country key.
The if statement serves as a type guard because it directly checks for the type of the value. The country variable is guaranteed to be a string in the if block.
# Adding key-value pairs to the Map
You can use the Map.set method to add a key-value pair to the Map.
Copied!const map1 = new Mapstring, string | number>([ ['name', 'Bobby Hadz'], ['age', 30], ]); map1.set('country', 'Germany'); console.log(map1.get('country')); // 👉️ "Germany" console.log(map1.delete('country')); // 👉️ true console.log(map1.has('country')); // 👉️ false
Make sure that the type of the key and value you passed to the Map.set() method conform to the types you specified when defining the Map.
The code sample shows how to use the Map.delete() method to remove a key-value pair from the Map.
The Map.delete() method returns true if an element in the Map object existed and has been removed, otherwise false is returned.
The Map.has() method returns true if the specified key exists in the Map .
# Iterating over the Map
You can use the Map.forEach method to iterate over a Map in TypeScript.
Copied!const map1 = new Mapstring, string | number>([ ['name', 'Bobby Hadz'], ['age', 30], ]); map1.forEach((value, key) => console.log(value, key); // 👉️ Bobby Hadz name, 30 age >); for (const [key, value] of map1) console.log(key, value); // 👉️ name Bobby Hadz, age 30 >
The Map.forEach() method and a for. of loop can be used to iterate over a Map .
The for. of loop might be your preferred approach if you have to use the break keyword to exit the loop prematurely.
Using the break keyword is not supported in the forEach method.
# Converting the Map’s keys or values to an array
Something you might often need is to convert the Map’s keys or values to an array, because arrays have many useful built-in methods.
Copied!const map1 = new Mapstring, string | number>([ ['name', 'Bobby Hadz'], ['age', 30], ]); const values = Array.from(map1.values()); console.log(values); // 👉️ ['Bobby Hadz', 30] const keys = Array.from(map1.keys()); console.log(keys); // 👉️ ['name', 'age']
We used the Map.values method to get an iterator object containing the values of the Map .
We passed the iterator as the only parameter to the Array.from method.
The Array.from method converts the iterable into an array and returns the new array instance.
# Getting the length of the Map
Another common thing you might need is to check how many elements a Map has. The Map.size method returns the number of elements in the Map.
Copied!const map1 = new Mapstring, string | number>([ ['name', 'Bobby Hadz'], ['age', 30], ]); console.log(map1.size); // 👉️ 2 map1.set('country', 'Germany'); console.log(map1.size); // 👉️ 3
# Deleting all key-value pairs from the Map
If you need to remove all key-value pairs from a Map object, use the Map.clear method.
Copied!const map1 = new Mapstring, string | number>([ ['name', 'Bobby Hadz'], ['age', 30], ]); map1.clear(); console.log(map1.size); // 👉️ 0
# Additional Resources
You can learn more about the related topics by checking out the following tutorials:
I wrote a book in which I share everything I know about how to become a better, more efficient programmer.