When I started working on data structures in TypeScript, I had to use objects with dynamic string keys, like user names or settings. Initially, I used basic object types, but this approach made it easy to miss errors or use incorrect values. Later, I found out about TypeScript features like mapped types and the keyof operator, which helped in writing code with type safety.
In this blog, I’ll explain how to use TypeScript Objects with Advanced Type Safety with string keys to improve type safety in code.
Understanding the Need for String Keys in TypeScript Objects
When working with objects in TypeScript, it’s common to encounter situations where the keys are dynamically generated or come from external sources. In such cases, using string keys becomes essential. However, this can lead to less type-safe code if not handled properly. Let’s consider an example:
const userDetails = {
"John Doe": { age: 30, city: "New York" },
"Jane Smith": { age: 25, city: "Los Angeles" },
};
console.log("User Details:", userDetails);
console.log("John's Age:", userDetails["John Doe"].age);Output:

In this example, we have an object userDetails where the keys are user names as strings. Accessing properties using string keys can be error-prone if we don’t have proper type checks in place.
Check out: Mapped Types in TypeScript
Leveraging TypeScript’s Mapped Types
TypeScript provides a powerful feature called mapped types that allows us to create new types based on existing ones. We can use mapped types to define the shape of an object with string keys. Here’s an example:
type UserDetails = {
[key: string]: {
age: number;
city: string;
};
};In this code snippet, we define a type UserDetails using a mapped type. The [key: string] syntax indicates that the object can have any string as a key, and the corresponding value is an object with age and city properties.
By applying this type to our userDetails object, TypeScript will enforce type checks and provide better type safety:
const userDetails: UserDetails = {
"John Doe": { age: 30, city: "New York" },
"Jane Smith": { age: 25, city: "Los Angeles" },
};
console.log("Mapped User Details:", userDetailsMapped);Output:

Now, if we try to assign a value with an incorrect shape to userDetails, TypeScript will raise an error, preventing potential bugs.
Check out: Extend Interfaces with Classes in TypeScript
Real-World Example: User Preferences
Let’s consider a real-world scenario where using string keys in TypeScript objects can be beneficial. Suppose you’re building a user preferences feature for your application, where each user can have their own set of preferences stored as key-value pairs.
type UserPreferences = {
[key: string]: string | number | boolean;
};
const userPreferences: UserPreferences = {
"theme": "dark",
"fontSize": 16,
"notifications": true,
};By defining the UserPreferences type using a mapped type, you can ensure that the userPreferences object adheres to the expected structure. TypeScript will enforce type checks, preventing accidental assignment of incorrect types to the preferences.
Using the keyof Operator
TypeScript’s keyof operator is another useful tool when working with objects and their keys. It allows you to create a union type of all the keys of an object type. Let’s see how it can be used:
type UserPreferencesKeys = keyof UserPreferences;In this example, UserPreferencesKeys will be a union type of all the possible keys in the UserPreferences type. This can be helpful when you need to restrict the keys to a specific set of values.
Real-World Example: Validating User Input
Suppose you have a form where users can update their preferences. You want to ensure that the submitted data contains only valid preference keys. Here’s how you can leverage the keyof operator:
function updateUserPreferences(userId: string, preferences: Partial<UserPreferences>) {
const validKeys: UserPreferencesKeys[] = ["theme", "fontSize", "notifications"];
for (const key in preferences) {
if (validKeys.includes(key as UserPreferencesKeys)) {
// Update the user's preferences
// ...
} else {
console.warn(`Invalid preference key: ${key}`);
}
}
}Output:

Check out: Conditionally Add Property to Object in TypeScript
In this function, we define an array validKeys that contains the allowed preference keys. By using UserPreferencesKeys, we ensure that the keys are of the correct type. Inside the loop, we check if the submitted key is included in the validKeys array before updating the user’s preferences. This helps maintain data integrity and prevents unauthorized modifications.
Combining Mapped Types and keyof
You can combine mapped types and the keyof operator to create even more advanced type definitions. Let’s consider an example where we want to create a type that represents a subset of properties from another type.
type UserPreferencesKeys = keyof UserPreferences;
const validKeys: UserPreferencesKeys[] = ["theme", "fontSize", "notifications"];
console.log("Valid preference keys:", validKeys);Output:

In this code snippet, we define a new type UserDetailsSubset using a mapped type. The K in keyof UserDetails syntax iterates over all the keys of the UserDetails type. The ? after the key indicates that the properties are optional. UserDetails[K] retrieves the type of the corresponding property from the UserDetails type.
Check out: Check If Object Is Undefined in TypeScript
Real-World Example: Partial User Details
Suppose you have a component that displays a summary of user details. You only need a subset of the user’s information, such as their name and city. Here’s how you can utilize the UserDetailsSubset type:
function renderUserSummary(userSubset: UserDetailsSubset) {
const city = userSubset["John Doe"]?.city;
const age = userSubset["John Doe"]?.age;
console.log(`Rendering summary for John Doe: City - ${city}, Age - ${age}`);
return `
<div>
<h2>${city ?? "Unknown City"}</h2>
<p>Age: ${age ?? "Unknown Age"}</p>
</div>
`;
}
renderUserSummary(userSubset);Output:

In this example, the renderUserSummary function accepts a UserDetailsSubset object. It extracts the city and age properties from the subset using the [“John Doe”] key. The ?. operator is used to safely access the properties, avoiding potential errors if the key doesn’t exist.
Check out: Merge Objects in TypeScript
By leveraging mapped types and the keyof operator, you can create flexible and type-safe code when working with objects and their subsets.
Conclusion
In this TypeScript tutorial, we have learned how to create TypeScript objects with string keys to provide type safety to code. We utilized features such as mapped types and the keyof operator to enhance the type safety of code. With real examples like user details and preferences, we have learned how these methods help catch errors and keep code secure.

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.