ObjectEntries

Challenge

Implement the type version of Object.entries

For example

interface Model {
  name: string;
  age: number;
  locations: string[] | null;
}
type modelEntries = ObjectEntries<Model>; // ['name', string] | ['age', number] | ['locations', string[] | null];

Solution

Auch zur Lösung dieses Problems erstellen wir wieder einen Mapped-Type, bei dem wir mittels des -?-Operator das Vorhandensein erzwingen Weiterhin müssen wir noch einen Hilfstypen definieren, über den wir undefined aus einer Vereinigung entfernen. Anschließend erhalten wir über keyof T am Ende des Mapped Types alle Werte (in dem Fall die Tupel aus Eigenschaft und Wert) als Vereinigung, und haben somit das Ergebnis.

type RemoveUndefined<T> = [T] extends [undefined] ? T : Exclude<T, undefined>;
type ObjectEntries<T> = {
  [K in keyof T]-?: [K, RemoveUndefined<T[K]>];
}[keyof T];

Eine weitere Lösung besteht darin, rekursiv den Typen aufzurufen, und dabei eine Vereinigung zu erstellen:

type RemoveUndefined<T> = [T] extends [undefined] ? T : Exclude<T, undefined>;

// Vereinigungen in konditionellen Typen sind distributiv, daher koennen wir hierdurch ueber jeden Wert laufen und eine finale Vereingung aus [Eigenschaft, Wert] erstellen
type RecordToUnion<T extends Record<string, any>, U = keyof T> = U extends U
  ? U extends keyof T
    ? [U, RemoveUndefined<T[U]>] | RecordToUnion<T, Exclude<T, U>>
    : []
  : never;
// Da als Base-Case ein leerer Array definiert wurde, muessen wir diesen noch aus der Vereinigung entfernen
type ObjectEntries<T extends Record<string, any>> = Exclude<
  RecordToUnion<T>,
  []
>;

References