Jul 24 '23 ~ 13 ~ 3 mins

Generate array of all an interface's keys with Typescript

When working with a large and complex code base like AG Grid it is very easy to miss updating certain parts of the code base.

For example, we have code generators that depend on having accurate lists of interface property names. In other words given a Typescript interface how can you produce an array that contains every property and ensure it stays up to date.

There are three approaches you might take to keeping this array of properties accurate:

1: Add a BIG comment reminding devs to update another part of the codebase.

// IF YOU UPDATE THE COLDEF INTERFACE REMEMBER TO ALSO UPDATE...

2: Delve deep into the world of ASTs to extract / generate the list based off the code file.

3: Use Typescript to enforce the link between ColDef properties and a manually maintained list.

// See below for an explanation
type ColDefObject = Record<(keyof ColDef), undefined>;

1. Add a BIG COMMENT

The first approach is very simple and works initially. However, as the team grows, or you get forgetful it can still be easily missed. This is true no matter how big and attention grabbing you try to make your comment. So it's probably best not to go for this approach. Secondly, its litters the code base with unsightly comments.

2. Use an AST Parser and code Generator

The second approach requires a lot more work! Yes it will ensure the property list is perfectly accurate, but now you have to maintain a code parser and manage additional build complexity. While this isn't beyond the realms of possibility, (we have a few of these in AG Grid) it is definitely not an easy task. We can safely say that this approach is totally overkill for what we are trying to solve, especially with the next option.

3. Use Typescript to enforce the updates

The third option really is the best of both worlds. There is no need for a big comment because your Typescript build will fail if the property list is out of sync. Secondly, we don't need to write a code generator, we have a perfectly good human developer who can fix the issue in seconds.

What makes this very easy is that they will get autocompletion and a clear description of what property they need to add to the list. This approach is both simple to maintain and also is 100% reliable.

How to use Typescript

So how can we use Typescript to achieve our desired result? As a reminder we want to have a complete array of all the property names defined on the ColDef interface.

It is worth clarifying we want an actual array of property names that we can use in our code as opposed to just having the list of possible properties in Type land. This is why (keyof ColDef)[] alone is not sufficient.

First off lets show a cut down version of the ColDef interface.

interface ColDef {
  headerName: string;
  columnGroupShow: boolean;
  toolPanelClass: {string: string};
  valueGetter: (params: ValueGetterParams) => any;
}

As you can see we have a mixture of different property types including primitive types as well as objects and functions.

Using the ColDef interface we can create a new type that will enable us to type an object from which we can extract all the ColDef keys from. We can do this using the Typescript Record utility type.

type ColDefObject = Record<(keyof ColDef), undefined>;

This type enforces that the object has every property from the ColDef interface defined and set to undefined. As we do not care about the actual values we give all the properties the undefined type.

With this we can produce the following colDefProperties object using autocompletion in our IDE thanks to Typescript.

const colDefProperties: ColDefObject = {
  headerName: undefined,
  columnGroupShow: undefined,
  toolPanelClass: undefined,
  valueGetter: undefined,
};

Then to produce the complete list of ColDef property names we can then pass this to Object.keys. We cast back to (keyof ColDef)[] as otherwise ALL_PROPERTIES will be typed as string[] which is not so helpful to us.

const ALL_PROPERTIES = Object.keys(colDefProperties) as (keyof ColDef)[];

Conclusion

A bit of time spent finding the correct Typescript type can avoid the need to manually maintain code and ensure consistency without the need for complex code generators.


Headshot of Stephen Cooper

Hi, I'm Stephen. I'm a senior software engineer at AG Grid. If you like my content then please follow / contact me on 🦋 Bluesky or 𝕏 (X) and say hello!