The Art of Type Arguments 9 exercises

Two Approaches for Typing Object Keys

As mentioned, there are two solutions to this challenge.

Solution 1

The first solution is to represent the generic as the entire object in the generic slot.

TObject extends object, and it returns Object.keys(obj) as Array<keyof TObject>:

const typedObjectKeys = <TObject

Loading solution


0:00 We have our function typedObjectKeys. This is the first solution. There are two. Inside here, we're representing the generic as the entire object. You pass in the obj and you say TObject here, and it returns Object.keys (obj) as Array keyof Object. Why is it using as? Let's check this out.

0:20 We could put this on the return type here, but then we get an error because Object.keys' actually still returning an array of strings. We need to tell TypeScript to say, "Could you just force this to be Array keyof TObject?" An Array keyof TObject is basically what we're trying to access here.

0:39 If we add something else in here, if we add c, then this is going to be Array keyof TObject, which resolves to a or b or c in an array. That's the first option.

0:50 What we can see here is that we get the entire object put into the generic slots, which is fine, really, because it's quite idiomatic. It makes sense. We're passing in an argument here. Why wouldn't that be represented in the generic slots?

1:04 Inside here, though, we are representing just the keys. This is quite a subtle distinction. We have Object extends object here. We'll talk about this in a minute. This is representing the entire object, whereas this is just representing the keys, so our argument needs to slightly change here.

1:24 Instead of it being the entire object inside here, it's now a Record with TKey and any. We actually do not care about what they put as the values of their objects because we only care about the keys. This is a typedObjectKeys. If it were a typedObjectValues, then that would be a different story.

1:42 Here, then, we can see that a or b is put into the generic slot here. This is really nice and maybe even a little bit nicer. It's really nice to capture, very precisely, only the thing you care about in the type argument.

1:57 What you get is result1, a or b, same result as before. We can add a third slot here if we want to, and boom, boom, boom.

2:05 One thing to note here is I've used TObject extends object here. Why didn't I do something like this, or why did I bother with this at all? Well, this is an interesting little tidbit on how to integrate with external libraries.

2:21 What I did was I Command-clicked on Object.keys. I saw that it takes an object here. I thought, "This is actually relatively unusual. I don't often see this. I don't often use this myself," and so I went, "OK, so object has to be a object of some kind."

2:38 Argument of type TObject is not assignable to parameter of type object, so we'll say TObject extends object here just to make it happy. This means you can't pass in anything Object.keys would usually provide, whereas this, I guess it's slightly tighter, maybe. I don't know.

2:59 I really wanted to call this module the art of generics because this is the art. You're trying to figure out how best to represent these generics in terms of the things that you're calling, in terms of the things that people are passing in.

3:12 I think this is a really nice example of it. Even a simple example like this can have little decisions which demand your attention. It doesn't really matter which one of these you end up choosing, but you should feel confident in your choice, and you should understand why you've made that choice.

3:28 With typedObjectKeys, only the strings get put into the generic slots, which is nice. Whereas here, you maybe got a cleaner one-to-one mapping between the object that you're passing in and the object that you're passing to Object.keys.