TypeScript – keyof Constraint with Generics

Using keyof in TypeScript Generic Function Constraints

We commonly use Generics when developing applications with TypeScript. This post will demonstrate how to use the keyof operator as part of your generic function constraints.

I’ll use some example code that was based on a production API built to extract keys from a dictionary. To start, you’ll find a brief review of constraints, and then the post will finish with extending the function to use keyof in the definition.

TypeScript Constraints with Generic Functions

Broadly speaking, you can use constraints in a generic function as follows shown below.

First, let’s start with a counter example. Imagine the following generic function:

function createClient<T, U>(primaryData: T, altData: U) {

    return Object.assign(primaryData, altData);

}

The issue with this code is that I can pass any data to the function, and it will compile. See this:

TypeScript unconstrained generic function.

That’s not even close to the intended use of this function, which should be taking two objects and combining them. Again though, this will compile, and the Object.assign() call will fail silently and  return 75.

In order to enforce that two objects must be passed in for my parameters, I’ll add a constraint on the generics, giving me something like:

function createClient<T extends object, U extends object>(primaryData: T, altData: U) {

    return Object.assign(primaryData, altData);

}

const newClient = createClient({clientName: "ABC Corp"}, 

                               {clientIndusty: "Manufacturing"});

Now, if I try to pass anything other than objects as parameters, the compiler will fail thanks to TypeScript constraints. As another added side effect, the TypeScript compiler will dynamically pickup on the type of objects that were passed in and correctly determine/provide access to their properties without failing compilation:

TypeScript constrained function and destructure.

Using keyof in the Constraints

In the production API, I am trusting the user to pass me an object and a property name so that I can extract and return a value of a property. This requires me to use the keyof keyword to kind of “type guard” lookup.

function createClient<T extends object, U extends object>(primaryData: T, altData: U) {
    return Object.assign(primaryData, altData);

}

function extractForHash<T extends object, U extends keyof T>(obj: T, key: U) {
    return obj[key];

}

const newClient = createClient({ clientName: 'ABC Corp' }, 

                               { clientIndustry: 'Manufacturing' });

const [client, industry] = [newClient.clientName, newClient.clientIndustry];

const hashedIndustry = doHash(extractAndHash(newClient, 'clientIndustry'));

 

Notice above, in extractAndHash, the use of keyof T. If I had tried to extend any other data type (e.g. string), TypeScript would not have successfully compiled.

This is a great example of one of those TypeScript features that help you avoid mistakes while, at the same time, writing clean and concise code.



Categories: Software Development, TypeScript

Tags:

Leave a Reply

%d bloggers like this: