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 provide this functionality out of the box.
Look at this User object:
type User = {
id: string;
name: string;
address: {
street: string;
city: string;
};
};
You want “id”, “name”, and “address.street”. The standard approach is insufficient:
// little helper to prettify the type on hover
type Pretty<T> = {
[K in keyof T]: T[K];
} & {};
type UserKeys = keyof User;
type PrettyUserKeys = Pretty<UserKeys>;
This approach returns the top-level keys, missing nested properties like “address.street”.
We need a more sophisticated solution using TypeScript’s advanced features:
- Conditional Types (if-then for types)
- Mapped Types (change each part of a type)
- Template Literal Types (make new string types)
- Recursive Types (types that refer to themselves)
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;
Let’s break down this type definition:
- We check if T is an object.
- For each key in the object:
- We either preserve the key as-is, or
- If the key’s value is another object, we combine the key with its nested keys
- We apply this to the entire type structure
Now let’s use it:
type UserKeys = ExtractKeys<User>;
This gives us all keys, including nested ones.
The practical benefits become clear in this example:
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");
TypeScript detects potential errors during development.
Important Considerations:
- This type implementation may impact performance with complex nested objects.
- The type system enhances development-time safety without runtime overhead.
- Consider the trade-off between type safety and code readability.
Wrap-Up
We’ve explored how to extract all keys from nested TypeScript objects. This technique provides enhanced type safety for your data structures. Consider the performance implications when implementing this in your projects.