Yuzhe's Blog

yuzhes

ObjectEntries

ObjectEntries

Challenge Link

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:

  1. Iterate over each key in the object
  2. Create a tuple [Key, Value] for each property
  3. 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:

  1. Remove all optional modifiers
  2. This also removes the undefined that was added due to optionality
  3. Keep any explicit undefined in union types (like string | 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:

  1. First, we create a mapped type with transformed values
  2. Then, we index into it with keyof T to get a union of all values

Edge Cases

Consider these scenarios:

Key Takeaways

  1. -? removes optionality - Essential for correctly typing entries
  2. Mapped type to union pattern - { [K in keyof T]: F<K> }[keyof T] converts to union
  3. Optional vs explicit undefined - They behave differently and require careful handling
  4. Required<T> removes both ? and the undefined it adds