TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
63: Key Remapping in Mapped Types
I once spent an entire afternoon fighting with a legacy API that had a very peculiar requirement: every property I sent to the server had to be prefixed with set_, and every property I read back had to start with get_. I had my clean User interface, and I tried to use a standard mapped type to create these request types. I quickly hit a wall. I could change the value of the properties to be strings or numbers, but I couldn't change the name of the keys themselves. I felt like I was staring at a locked door until I discovered key remapping.
Up until now, you've used mapped types to iterate over keys and change their types—essentially saying, "For every key in this object, make its value a boolean." Key remapping allows you to say, "For every key in this object, change the key's name to this, and then make its value a boolean." It's the difference between painting the walls of a room and actually moving the walls.
Transforming Keys with the as Clause
The magic happens with the as keyword inside the mapped type's key definition. Instead of just [K in keyof T], you add as NewKeyName. This tells TypeScript to map the original key to a new identity before assigning the value type.
type User = {
name: string;
email: string;
age: number;
};
// We want to turn { name: string } into { getName: () => string }
type Getters = {
[K in keyof T as `get${Capitalize}`]: () => T[K];
};
type UserGetters = Getters<User>;
/*
Result: {
getName: () => string;
getEmail: () => string;
getAge: () => number;
}
*/
Notice how I used string & K? That's a little trick to tell TypeScript that K is definitely a string, which lets us use template literal types. If you don't do that, TypeScript might complain that K could be a symbol, and you can't interpolate a symbol into a string template.
Filtering Keys Out of a Type
One of the most powerful, and slightly less obvious, uses of key remapping is filtering. Since the as clause can map a key to never, TypeScript will simply drop that key from the resulting object. I use this constantly when I need to strip out specific types of properties from a large API response before passing them to a component.
type Product = {
id: string;
title: string;
price: number;
internalCode: string;
lastUpdated: Date;
};
// Only keep keys that are strings
type StringOnly = {
[K in keyof T as string extends T[K] ? K : never]: T[K];
};
type ProductStrings = StringOnly<Product>;
/*
Result: {
id: string;
title: string;
internalCode: string;
}
(price and lastUpdated are gone!)
*/
It feels a bit like a hack at first, but once you realize you can use a ternary operator inside the as clause, you've essentially gained the ability to "filter" your types. It's significantly cleaner than trying to manually define a bunch of partial types every time the backend team adds a new field to a model.
📋 Practical Task
Building a Feature Permission Mapper
Imagine you are building a dashboard where different features are enabled based on user roles. You have a base type that lists the features of your application, but your permission system requires these keys to be transformed into "can_" prefixed booleans.
Your Task:
- Create a type called
AppFeatureswith the following keys:editProfile,deleteUser,viewAnalytics, andmanageBilling(all should be strings for the base type). - Create a mapped type called
Permissionsthat takes a typeTand transforms every key by prefixing it withcan_and changing the value type to aboolean. - Apply the
Permissionstype toAppFeaturesto create a type calledUserPermissions.
Your final UserPermissions type should look like this:
{ can_editProfile: boolean; can_deleteUser: boolean; ... }
There are no comments for now.