Designing Your Types 11 exercises

Use a Mapped Type to Create a New Object Shape

Our challenge is to derive the shape of AttributeGetters based on the Attributes interface:

interface Attributes {
firstName: string;
lastName: string;
age: number;

Before we solve this challenge, let's see how a naive approach using a Record type would fail.


Loading solution


00:00 So your initial inclination here might be to do some sort of record type where you say record key off attributes. And then you say it's like a short a function that returns.

00:12 Oh, now this is tricky. Because if you just say it returns a string, then you're, you're really close there. You've got first name, last name, age, all returning string. So first name is correct. Last name is correct. But age is not correct.

00:24 Because in this little closure here, when we say, like a function that returns a string, we don't have access to the current key that we're on when we're iterating over this. So we need some other way of doing this where we say, okay, for this key, add this behavior or add this value into the add this type into the value.

00:44 So the way we can do that is by opening up some curly braces. And inside here, you might think we're about to do an index signature. Not quite. This is a mapped type. Very, very similar. And the map type looks like this. We say K in key off attributes. And we're essentially saying here for each member of this union for this union of key off attributes, we're going to create a property on a new object.

01:10 And let's first of all, just say, let's say function that returns a string. Okay, now we hover over this, you can see we're getting exactly the same output. But there's a big difference. In this case, we now have access to the key that we're on. So let's say it's a function that returns the key that we're on. There we go. First name returns first name, last name returns last name, age returns age.

01:32 And now we can say instead of returning the key that we're on, we can use that key to index into attributes. And now we get the real stuff. So now attributes first name is going to be and let's just actually put this in a tuple so we can see the keys as well. So attributes first name is a string, attributes last name is a string, attributes age is a number. Amazing. So we delete that, and we just end up with the object that we want.

02:02 This is so, so powerful. And bear in mind that it doesn't have to be like a lot of people get this wrong, they think map types always have to be a key off here. But they don't, you can just have like key A or type example equals A or B, or C, for instance. And let's say type mapped example. And we say E in example, and we just say E.

02:26 Now what you end up with this mapped example is A, A, B, B, C, C. So you don't have to map over key of anything, you can just map over a union of something that's assignable to a string. So if I say example, and I give it like an object with a string, that's going to yell at me because a string is not assignable to a property key. You can't use a string as a property key in JavaScript.

02:50 So this is how you get started with map types. And you can start to see that transforming objects into other shapes is a really great way to derive types from other types, because we still have attributes, which is a single source of truth. And now if I say like is admin, then I get a Boolean here, then I get attribute getters is admin is admin returns a Boolean. So I have a central space where I get to define what my actual types are, and then I get to transform them later.

03:19 So cool.