Generate array of all an interface's keys with Typescript
Tags: typescript
Published: 2023-07-24 21:09:34

When working with a large and complex code base like [AG Grid](https://ag-grid.com/) 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.
   ```ts
   // 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.

```ts
// 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. 

```ts
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](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type) utility type.

```ts
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.

```ts
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.

```ts
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.