ObjectEntries
ObjectEntries
Problem
Implement the type version of Object.entries.
interface Model {
name: string;
age: number;
locations: string[] | null;
}
type modelEntries = ObjectEntries<Model>;
// ['name', string] | ['age', number] | ['locations', string[] | null]Solution
Approach
The key insight is that Object.entries returns an array of key-value pairs. In TypeScript's type system, we need to:
- Iterate over each key in the object
- Create a tuple
[Key, Value]for each property - Return a union of all these tuples
The tricky part is handling optional properties. When a property is optional (key?: Type), its value type becomes Type | undefined. But Object.entries doesn't include the undefined - it just may or may not include the entry.
Basic Solution
type ObjectEntries<T> = {
[K in keyof T]-?: [K, T[K]]
}[keyof T]Wait - this doesn't quite work. The -? removes the optional modifier, but T[K] still includes undefined for optional properties.
Handling Optional Properties
We need to strip undefined from the value type when it comes from an optional property:
type ObjectEntries<T> = {
[K in keyof T]-?: [K, T[K] extends undefined ? undefined : Exclude<T[K], undefined>]
}[keyof T]But this has a problem - what if undefined is an explicit part of the type (not from optionality)?
Final Solution
type ObjectEntries<T, U = Required<T>> = {
[K in keyof U]: [K, U[K]]
}[keyof U]By using Required<T>, we:
- Remove all optional modifiers
- This also removes the
undefinedthat was added due to optionality - Keep any explicit
undefinedin union types (likestring | undefined)
However, this approach strips undefined even when explicitly declared. A more precise solution:
type RemoveUndefined<T> = [T] extends [undefined] ? T : Exclude<T, undefined>
type ObjectEntries<T> = {
[K in keyof T]-?: [K, RemoveUndefined<T[K]>]
}[keyof T]This removes undefined unless the type is only undefined.
Deep Dive
Why -? Modifier?
The -? modifier removes the optional marker from properties. Without it, optional properties would create entries like ['key', Type | undefined].
Mapped Type to Union
The pattern { [K in keyof T]: ... }[keyof T] is a common idiom:
- First, we create a mapped type with transformed values
- Then, we index into it with
keyof Tto get a union of all values
Edge Cases
Consider these scenarios:
- Optional properties:
{ name?: string }→['name', string] - Explicit undefined:
{ name: string | undefined }→['name', string | undefined](should keepundefined) - Only undefined:
{ name: undefined }→['name', undefined]
Key Takeaways
-?removes optionality - Essential for correctly typing entries- Mapped type to union pattern -
{ [K in keyof T]: F<K> }[keyof T]converts to union - Optional vs explicit undefined - They behave differently and require careful handling
Required<T>removes both?and theundefinedit adds
