DEV Community

Alexander Opalic
Alexander Opalic

Posted on • Originally published at alexop.dev on

TypeScript Tutorial: Extracting All Keys from Nested Object

What's the Problem?

Let's say you have a big TypeScript object. It has objects inside objects. You want to get all the keys, even the nested ones. But TypeScript doesn't make this easy.

Look at this User object:

type User = {
  id: string;
  name: string;
  address: {
    street: string;
    city: string;
  };
};

Enter fullscreen mode Exit fullscreen mode

You want "id", "name", and "address.street". But TypeScript just shrugs. The usual way? It's useless:

type UserKeys = keyof User;
Enter fullscreen mode Exit fullscreen mode

This only gives you the top-level keys. It misses the nested goodies like "address.street".

So, we need to get clever. We'll use some TypeScript magic:

  1. Conditional Types (if-then for types)
  2. Mapped Types (change each part of a type)
  3. Template Literal Types (make new string types)
  4. Recursive Types (types that refer to themselves)

Don't panic. It's not as scary as it sounds.

Here's our solution:

type ExtractKeys<T> = T extends object
  ? {
      [K in keyof T & string]: 
        | K 
        | (T[K] extends object ? `${K}.${ExtractKeys<T[K]>}` : K);
    }[keyof T & string]
  : never;

Enter fullscreen mode Exit fullscreen mode

Yes, it looks like a cat walked on your keyboard. But it works. Let's break it down:

  1. We check if T is an object.
  2. If it is, we look at each key.
  3. For each key, we either keep it as-is or...
  4. If the key's value is another object, we add the key, a dot, and all the keys inside it.
  5. We do this for all keys.

Now let's use it:

type UserKeys = ExtractKeys<User>;

Enter fullscreen mode Exit fullscreen mode

Voila! We've got all the keys, even the nested ones.

Why bother? It makes your code safer. Look:

const user: User = {
  id: "123",
  name: "John Doe",
  address: {
    street: "Main St",
    city: "Berlin",
  },
};

function getProperty(obj: User, key: UserKeys) {
  const keys = key.split(".");
  let result: any = obj;

  for (const k of keys) {
    result = result[k];
  }

  return result;
}

// This works
getProperty(user, "address.street");

// This gives an error
getProperty(user, "address.country");

Enter fullscreen mode Exit fullscreen mode

TypeScript catches your mistakes before they blow up in your face.

Remember

  1. This type can be slow for very big objects.
  2. It doesn't change how your code runs. It only helps catch errors early.
  3. It can make your code harder to read. Use it wisely.

Wrap-Up

We've learned to wrangle all the keys from nested TypeScript objects. It's like having x-ray vision for your data. But remember, with great power comes great responsibility. And slower compile times.

Top comments (0)