Skip to Content
Course content

207: Practice Exercise: Building a Type-Safe Path Getter (Deep Get)

Click on the "Edit" button in the top corner of the screen to edit your slide content.

We've all been there: you're dealing with a massive API response or a deeply nested configuration object, and you find yourself writing if (user && user.profile && user.profile.address) just to get to a zip code. It's tedious. Naturally, the first instinct is to write a helper function—a "deep get"—that takes a path string like "profile.address.zip" and does the digging for you.

The "Just Trust Me" Approach

If you're in a rush, you might write something like this. It's clean, it's short, and it works at runtime:

function getDeepValue(obj: any, path: string) {
  return path.split('.').reduce((acc, key) => acc?.[key], obj);
}

const data = { user: { settings: { theme: 'dark' } } };
const theme = getDeepValue(data, 'user.settings.theme'); // returns 'dark'

From a JavaScript perspective, this is fine. But from a TypeScript perspective, we've just committed a crime. Look at the return type of theme: it's any. We've essentially told the compiler, "Stop helping me; I'll handle it." The moment you use theme elsewhere, you lose all autocomplete and type safety. Even worse, if you typo the path as 'user.setttings.theme', TypeScript won't blink. You'll just get undefined at runtime, and you'll spend twenty minutes debugging a typo that the compiler should have caught in milliseconds.

The Cost of the Lie

The problem here is a disconnect between the path string and the actual structure of the obj. To the compiler, "user.settings.theme" is just a string. It has no inherent relationship to the keys of your object. To fix this, we need to move away from string and toward Template Literal Types. We want a type that says: "This string must be a valid key of the object, or a valid key of the object followed by a dot and then a valid key of the nested object."

Mapping the Path with Recursive Types

This is where things get interesting. We can use a recursive conditional type to map the path string to the actual value it points to. I usually approach this by thinking about the path in two parts: the first key (the "head") and the rest of the path (the "tail").

type PathValue = 
  P extends `${infer Key}.${infer Rest}` 
    ? Key extends keyof T 
      ? PathValue 
      : never 
    : P extends keyof T 
      ? T[P] 
      : never;

function deepGet<T, P extends string>(obj: T, path: P): PathValue<T, P> {
  return path.split('.').reduce((acc, key) => acc?.[key], obj) as any;
}

Let's break down what's happening in PathValue. We use infer Key to grab the part of the string before the first dot. If that Key exists on T, we recursively call PathValue using the value at that key and the Rest of the string. If we hit a string with no dots left, we check if that final segment is a key of the current object level. If it is, we return the type of that value. If at any point the path diverges from the object structure, it collapses to never.

Where the Complexity Hits the Fan

Now, I'll be honest with you: this isn't a free lunch. While this gives us incredible IDE support—you'll actually get autocomplete for the path strings now—it adds a layer of cognitive load. Recursive types can be a bit of a brain-bender when you come back to them six months later. There's also a performance cost; if you have an object that is ten levels deep and a path string that is equally long, the TypeScript compiler has to do a lot of work to resolve those types.

In a small project, this might be overkill. But if you're building a shared utility library or working on a mission-critical data layer, the trade-off is worth it. You're moving the "error" from the user's browser at runtime to the developer's editor at compile time. In my book, that's always a win.




📋 Practical Task

Exercise: Implementing a Type-Safe Deep-Get Utility for Configuration Objects

You are tasked with creating a type-safe configuration reader for a complex application settings object. The goal is to ensure that any attempt to access a configuration value using a path string is validated by TypeScript.

Requirements:

  • Define a Config interface with at least three levels of nesting (e.g., app.database.connection.timeout).
  • Implement the PathValue recursive type to resolve the type of a value based on a dot-notation string path.
  • Implement the deepGet function using the PathValue type for its return value.
  • Test your implementation by:
    1. Successfully retrieving a deeply nested value with full type inference.
    2. Intentionally passing an invalid path string to verify that TypeScript flags it as an error.

Starter Code:

interface Config {
  api: {
    endpoints: {
      users: string;
      posts: string;
    };
    timeout: number;
  };
  theme: {
    colors: {
      primary: string;
      secondary: string;
    };
    darkMode: boolean;
  };
}

// Your PathValue type and deepGet function here...
Rating
0 0

There are no comments for now.

to be the first to leave a comment.