Build a Type Safe Object Keys Helper
Build a generic helper that returns the keys of an object with precise types instead of string[]. Combine keyof, generics, and type assertions for safer iteration and lookup.
The built-in Object.keys function returns string[] regardless of the object type. This means you lose the specific key names and cannot safely use them for property access. A TypeScript object keys helper fixes this by using generics to preserve the exact key types.
The problem is easy to see. Given a typed object, calling Object.keys gives you a generic string array:
const user = {
id: 1,
name: "Ada",
email: "ada@example.com",
};
const keys = Object.keys(user);The variable keys has type string[], not ("id" | "name" | "email")[]. If you try to use one of those keys to access a property on user, TypeScript complains because it cannot guarantee the string is a valid key. This is intentional: at runtime, objects can have extra properties that the type does not declare.
The solution is a small generic helper that narrows the return type:
function typedKeys<T extends Record<string, unknown>>(obj: T): (keyof T)[] {
return Object.keys(obj) as (keyof T)[];
}The function accepts any object-like type and returns an array of its keys. The as assertion tells TypeScript to trust that Object.keys returns the object's actual keys.
This is safe because Object.keys does return the runtime keys. The assertion only changes the compile-time type, not what actually happens when the code runs.
Using the Helper
With the helper in place, you can iterate over an object's keys the same way you always have, but every key now has its precise literal type instead of a generic string:
const userKeys = typedKeys(user);
for (const key of userKeys) {
const value = user[key];
console.log(`${key}: ${value}`);
}TypeScript knows that each key is a valid property of user. The variable value has the correct type for each property, and you get full autocomplete on key inside the loop.
A Second Helper for Entries
A similar pattern gives you type-safe entries with both key and value correctly typed:
function typedEntries<T extends Record<string, unknown>>(
obj: T
): [keyof T, T[keyof T]][] {
return Object.entries(obj) as [keyof T, T[keyof T]][];
}Each entry is a tuple of a key and its corresponding value type. You can destructure both in a loop:
for (const [key, value] of typedEntries(user)) {
console.log(`${key} is ${typeof value}`);
}The value has the union of all property types, which is correct because each iteration could yield any property. If you need more precise typing per iteration, you can narrow the key inside the loop using a type guard.
How the Generic Constraint Works
The constraint T extends Record<string, unknown> ensures the helper only accepts objects with string keys. Record is a built-in utility type. The unknown value type means the helper works with objects of any shape.
Inside the return type, keyof T produces the union of the object's property names. For the user object, that is "id" | "name" | "email". The array type (keyof T)[] wraps this union in an array.
The type assertion on the return value is necessary because TypeScript cannot verify at compile time that Object.keys returns exactly those keys. The runtime behavior is correct, so the assertion is the standard approach. For more on type assertions, see type assertions and type casting in TypeScript.
When Not to Use This Helper
The helper is most useful when you control the object type and know its shape is accurate. Skip it when:
- The object comes from an external source and may have extra runtime properties not in the type.
- You are dealing with index-signature types where keyof produces string | number.
- You need to handle symbol keys, which keyof excludes by default.
In those cases, stick with Object.keys and narrow the keys manually where needed.
Why This Pattern Matters
Type-safe object keys are not just a convenience. They enable safer refactoring: if you rename a property, TypeScript flags every place the old key name is used. They also improve autocomplete in editors, so you see the actual property names when iterating instead of generic strings.
The helper is small enough to drop into a shared utilities file. It works with any object type without additional configuration. Once added, you get precise key types everywhere you iterate over objects in your codebase.
For more on the keyof operator that powers the keys helper, see keyof operator in TypeScript. For patterns that use these keys for type-safe property access, see indexed access types in TypeScript.
Rune AI
Key Insights
- Object.keys returns string[] by design, losing the precise key types.
- A generic helper with keyof and a type assertion restores the exact key union.
- The helper gives you autocomplete on keys and type-safe property access.
- Use a type assertion inside the helper, but keep the public API fully type-safe.
- The helper works with any object type, including inferred types from object literals.
Frequently Asked Questions
Why does Object.keys return string[] instead of the actual keys?
Is it safe to assert the return type with as?
Can this helper work with generic objects that have index signatures?
Conclusion
A type-safe object keys helper is a small utility with a big payoff. It replaces string[] with the actual key names, giving you autocomplete, type-safe iteration, and the ability to use keys in generic type operations. The implementation is simple: constrain the generic to an object shape, assert the return type, and let TypeScript verify the rest at compile time.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.