Patterns
Why does `Object.keys(config)` give me `string[]` instead of the keys I can see right there?
Because a value can have more keys than its type declares, and `Object.keys` returns all of them. Narrowing the return is a deliberate, local decision — not a fix for a bug.
The recipe
type Keys<T extends object> = (keyof T)[] const typedKeys = <T extends object>(value: T): Keys<T> => Object.keys(value) as Keys<T> const config = { retries: 3, verbose: true } const keys = typedKeys(config)
The build compiles this and checks each result below.
How it works
- 01
type Keys<T extends object> = (keyof T)[]
The shape you wish `Object.keys` had. Naming it separately keeps the cast in one place and readable.
- 02
Object.keys(value) as Keys<T>A cast, and an honest one: you are asserting the object has no keys beyond its type. For a literal you just built that is true; for something that arrived from elsewhere it may not be.
What you get
typeof keys→("retries" | "verbose")[]Keys<{ a: 1; b: 2 }>→("a" | "b")[](typeof keys)[number]
→"retries" | "verbose"And indexing by `number` recovers the union, which is usually what you wanted to iterate over.
Where it goes wrong
The cast is a lie whenever the object came from outside your program. Extra properties are legal — a value typed `{ a: 1 }` may carry `b` at run time — and this helper will hand you a key list that does not include it while claiming to be complete.
Takeaway
`Object.keys` returning `string[]` is not a design mistake; it is the type system being honest about extra properties. Use this helper on objects you built, not on ones you received.