Przejdź do treści
#245 object-paths · par 5

    ↑↓ ruch · ⏎ otwórz · esc zamknij

    Nr 245 · 28 sierpnia 2026 · Bezlitosna

    Ścieżki

    Zaimplementuj `Paths<T>`, aby tworzył unię wszystkich ścieżek z kropkami w zagnieżdżonym obiekcie — łącznie z pośrednimi, nie tylko liśćmi.

    01

    Spróbuj rozwiązać samodzielnie

    Par 5

    Zagadka

    object-paths.ts
    interface Config {
      db: { host: string; port: number }
      debug: boolean
    }
    Podejście 1 z 5Jeszcze nie sprawdzone

    Zastąp ??? — rozwiązanie sprawdzamy przypadkami poniżej. Tab wcina; naciśnij Escape, a potem Tab, by wyjść fokusem.

    Testy

    4
    • Paths<Config>
      'db' | 'db.host' | 'db.port' | 'debug'
    • Paths<{ a: string }>
      'a'
    • Paths<{ x: { y: { z: 1 } } }>
      'x' | 'x.y' | 'x.y.z'
    • Paths<{}>
      never

    Jak oceniamy test Dokładna równość typów, nie przypisywalność — przecięcie to nie to samo co spłaszczony obiekt.

    Jak poszło innym

    Tę zagadkę rozwiązało jak dotąd mniej niż 5 osób. Rozkład pojawi się, gdy próbka będzie na tyle duża, by coś znaczyć.

    Gra na krótkim dystansie

    Najmniej znaków

    Nie ma jeszcze publicznych wyników.

    Archiwum
    02

    Rozwiązanie z komentarzem

    Opublikowano 29 sierpnia 2026

    Rozwiązanie

    type Paths<T> = T extends object
      ? {
          [K in keyof T & string]: T[K] extends object
            ? K | `${K}.${Paths<T[K]> & string}`
            : K
        }[keyof T & string]
      : never

    Najczęstsza błędna odpowiedź

    type Paths<T> = T extends object
      ? {
          [K in keyof T & string]: T[K] extends object
            ? `${K}.${Paths<T[K]> & string}`
            : K
        }[keyof T & string]
      : never

    To daje tylko liście: `"db.host" | "db.port" | "debug"`. Gałąź dla klucza o wartości obiektowej zwraca potomków z przedrostkiem, ale zapomina, że sam klucz też jest poprawną ścieżką. To `K |` odpowiada za całe pojawienie się ścieżek pośrednich.

    Linia po linii

    1. }[keyof T & string]

      Typ mapowany to rusztowanie, nigdy odpowiedź. Zindeksowanie go wszystkimi własnymi kluczami odczytuje naraz każdą wartość i łączy je w unię — standardowy sposób zamiany obliczenia per-klucz w unię.

    2. Paths<T[K]> & string

      To przecięcie nie jest ozdobą. Literał szablonowy wstawi tylko coś, o czym kompilator już wie, że jest łańcuchopodobne, a odroczone wywołanie rekurencyjne takie nie jest — `& string` to obietnica, która je przepuszcza.

    3. keyof T & string

      Klucze obiektu mogą być symbolami, które nie mogą wystąpić w ścieżce tekstowej. Przecięcie ze `string` odsiewa je zarówno przy mapowaniu, jak i przy indeksowaniu — a oba miejsca muszą się zgadzać, bo inaczej wynik się zapada.

    Do zapamiętania

    Typ mapowany zindeksowany własnymi kluczami to odpowiednik `Object.values().flat()` na poziomie typów. W połączeniu z rekurencją przechodzi dowolnie głębokie struktury — i dokładnie na tym kształcie zbudowane są typowane biblioteki formularzy i walidatory kluczy i18n.

    Wykorzystuje