DEV Community

Alexander Opalic
Alexander Opalic

Posted on • Originally published at alexop.dev on

1 1 1 1 1

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.

Image of Datadog

The Future of AI, LLMs, and Observability on Google Cloud

Datadog sat down with Google’s Director of AI to discuss the current and future states of AI, ML, and LLMs on Google Cloud. Discover 7 key insights for technical leaders, covering everything from upskilling teams to observability best practices

Learn More

Top comments (0)

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up