Hone logo
Hone
Problems

Deeply Nested Object Property Getter

This challenge focuses on creating a robust TypeScript helper function that can safely retrieve a deeply nested property from an object. This is a common requirement when dealing with complex data structures or APIs where the presence of intermediate properties cannot be guaranteed, preventing runtime errors like "cannot read property '...' of undefined".

Problem Description

Your task is to implement a TypeScript function called getDeep that takes an object and a path to a property within that object. The path should be represented as a string of property names separated by dots (e.g., "user.address.street"). The function should return the value of the specified property if it exists, and undefined if any part of the path is null or undefined.

Key Requirements:

  • The function must handle nested objects and arrays. For arrays, the path should use numerical indices (e.g., "items.0.name").
  • The function must be type-safe, leveraging TypeScript's generics to infer the return type based on the provided path and the input object.
  • The function should gracefully handle cases where intermediate properties or array elements are null or undefined without throwing errors.

Expected Behavior:

  • If the path exists and leads to a value, return that value.
  • If any segment of the path is null or undefined, return undefined.
  • If the object itself is null or undefined, return undefined.

Edge Cases to Consider:

  • An empty path string.
  • A path that leads to null or undefined values.
  • A path containing invalid array indices (e.g., out of bounds, non-numeric).
  • An object with null or undefined as property values.
  • Paths involving both objects and arrays.

Examples

Example 1:

const data = {
  user: {
    name: "Alice",
    address: {
      street: "123 Main St",
      city: "Anytown"
    },
    orders: [
      { id: 1, item: "Book" },
      { id: 2, item: "Pen" }
    ]
  }
};

// Path to a nested string
getDeep(data, "user.address.street"); // Expected Output: "123 Main St"

// Path to a nested array element's property
getDeep(data, "user.orders.0.item"); // Expected Output: "Book"

Explanation: The function navigates through user, then address, and finally retrieves street. For the second case, it navigates through user, then orders, accesses the element at index 0, and retrieves its item property.

Example 2:

const data = {
  config: null,
  settings: {
    theme: "dark"
  }
};

// Path to a null property
getDeep(data, "config.timeout"); // Expected Output: undefined

// Path where an intermediate property is missing
getDeep(data, "settings.notifications.sound"); // Expected Output: undefined

Explanation: In the first case, config is null, so the function stops and returns undefined. In the second case, notifications does not exist on settings, so the function returns undefined.

Example 3:

const data = {
  users: [
    { id: 1, profile: { username: "bob" } },
    undefined, // An undefined element in the array
    { id: 3, profile: { username: "charlie" } }
  ]
};

// Path to a property within an undefined array element
getDeep(data, "users.1.profile.username"); // Expected Output: undefined

// Path to a property through a valid path with an undefined intermediate
getDeep(data, "users.0.profile.username"); // Expected Output: "bob"

Explanation: The first case attempts to access profile.username on an undefined element at index 1, resulting in undefined. The second case successfully retrieves the username from the first user's profile.

Constraints

  • The input obj can be any valid JavaScript object, including arrays, null, or undefined.
  • The path argument will be a string.
  • Performance: The function should be reasonably efficient, avoiding unnecessary deep recursion if possible. For typical nested objects, the execution time should be minimal.

Notes

  • Consider how to split the path string effectively.
  • TypeScript's conditional types and mapped types might be useful for achieving the desired type safety.
  • Think about how to handle array indices within the path.
  • The challenge is to create the function signature and implementation that correctly infers types. For example, calling getDeep(data, "user.address.city") should return a string, while getDeep(data, "user.age") (if age was a number) should return a number. If the path could lead to multiple types or undefined, the inferred type should reflect that union.
Loading editor...
typescript