Sitemap
JavaScript in Plain English

New JavaScript and Web Development content every day. Follow to join our 3.5M+ monthly readers.

Enhancing getProp() — From Simple Utility to Type-Safe Powerhouse

4 min readJul 17, 2025

--

In the first part of this article, we introduced the idea of refactoring utility functions with TypeScript, and how it dramatically improved the reliability and maintainability of my code. One of the key functions I revamped was getProp(), which accesses deeply nested properties in an object.

But after receiving some thoughtful feedback from Jerome, I realized there’s even more we can do! In this follow-up post, we’ll break down the improvements in a side-by-side comparison — comparing the old, untyped version of getProp() with the enhanced, type-safe version.

Let’s get into it!

Press enter or click to view image in full size
Enhancing getProp() — From Simple Utility to Type-Safe Powerhouse
Enhancing getProp() — From Simple Utility to Type-Safe Powerhouse

The Old Method: Accessing Nested Properties in Plain JavaScript

In the old version of getProp(), written in vanilla JavaScript, things were simple but risky. Here’s what it looked like:

function getProp(obj, path) {
const pathArray = path.split(".");
let result = obj;

for (let i = 0; i < pathArray.length; i++) {
result = result[pathArray[i]];
if (result === undefined || result === null) {
return undefined;
}
}
return result;
}

const testObj = {
user: {
name: "John",
posts: [
{ title: "Post 1", content: "This is post 1" },
{ title: "Post 2", content: "This is post 2" },
],
},
};

const postContent = getProp(testObj, "user.posts[0].content");

Key Limitations:

  1. No Type Safety: You can’t know the types of properties being accessed at compile time. If you try to access a non-existent property, you won’t get an error until runtime.
  2. Arrays are tricky: You can’t handle array indices well (posts[0]), as arrays aren’t treated as part of the object’s keys.
  3. Error-prone: Mistyped paths like "user.posts[0]" or "user.posts[0].content" will silently fail without warning, potentially leading to bugs.
  4. No Optional Chaining: If any part of the path is null or undefined, you’re at risk of encountering errors.

The Enhanced Method: Using TypeScript for Type Safety, Arrays, and Optional Chaining

In contrast, here’s the refactored version of getProp() using TypeScript with all the enhancements we’ve discussed so far. The added complexity pays off in terms of safety, flexibility, and clarity.

Refactored Code:

type ObjPath<TObj extends Record<string, any>> = {
[K in keyof TObj & string]: TObj[K] extends Array<any>
? `${K}[${number}]` | `${K}.${ObjPath<TObj[K][number]>}`
: TObj[K] extends Record<string, any>
? K | `${K}.${ObjPath<TObj[K]>}`
: K;
}[keyof TObj & string];

type ObjResult<TObj extends Record<string, any>, TObjPath extends ObjPath<TObj>> =
TObjPath extends `${infer K extends keyof TObj & string}[${infer Index}]`
? TObj[K][number]
: TObjPath extends `${infer K extends keyof TObj & string}.${infer Rest}`
? ObjResult<TObj[K], Rest>
: TObj[TObjPath];

function getProp<TObj extends Record<string, any>, TPath extends ObjPath<TObj>>(
obj: TObj,
path: TPath,
): ObjResult<TObj, TPath> | undefined {
const [current, ...rest] = path.split(".");
if (obj[current] == null) return undefined;

if (rest.length === 0) {
return obj[current];
}

return getProp(obj[current], rest.join("."));
}

const data = {
user: {
name: "John",
posts: [
{ title: "Post 1", content: "This is post 1" },
{ title: "Post 2", content: "This is post 2" },
],
},
};

const postContent = getProp(data, "user.posts[0].content");
// Returns "This is post 1" and TypeScript ensures it's a string!

Improvements:

  1. Type Safety:
    TypeScript ensures that we access properties correctly. The type of postContent is automatically inferred to be string, based on the object structure. Mistyped paths will result in compile-time errors (e.g., "user.posts[0]").
  2. Arrays Supported:
    Arrays are now handled as part of the path type. You can access array indices safely using posts[0]. TypeScript knows that data.user.posts[0].content is a string, so there’s no ambiguity.
  3. Optional Chaining:
    The refactored version gracefully handles null or undefined at any level of the path. If any part of the path doesn’t exist, it simply returns undefined, instead of throwing runtime errors.
  4. Recursive Type Inference:
    Using TypeScript’s recursive types, we can infer the type of deeply nested properties. For example, "user.posts[1].content" will return the correct type (string), and TypeScript ensures you can’t accidentally access the wrong type.

Additional Improvements Suggested by the Community

Thanks to the incredible feedback from Jerome, we also improved the function to address other concerns such as:

  • Caching Results for Performance:
    We added a simple cache to store previously accessed paths, ensuring that repeated calls for the same path don’t waste performance resources.
const cache: Record<string, any> = {};
function getPropWithCache<TObj extends Record<string, any>, TPath extends ObjPath<TObj>>(
obj: TObj,
path: TPath,
): ObjResult<TObj, TPath> | undefined {
if (cache[path]) return cache[path];
const result = getProp(obj, path);
cache[path] = result;
return result;
}
  • Better Array and Object Type Handling:
    The community helped extend the recursive ObjPath type to fully support arrays and nested objects, ensuring the function can scale to any depth.

Conclusion:

The enhanced getProp() function is a great example of how TypeScript’s powerful typing system can improve even the most basic utilities. By leveraging type safety, array support, optional chaining, and even caching, we can now build more robust and performant JavaScript applications.

Get Kavindu Kokila (Kavi Castelo)’s stories in your inbox

Join Medium for free to get updates from this writer.

👉 Connect with me: GitHub | LinkedIn

Thank you for being a part of the community

Before you go:

--

--

Kavindu Kokila (Kavi Castelo)
Kavindu Kokila (Kavi Castelo)

Written by Kavindu Kokila (Kavi Castelo)

Kavi Castelo is a tech lead and CEO. With 4+ years of experience in software development, he builds scalable systems and leads innovation in HR tech.

JavaScript in Plain English
JavaScript in Plain English

Published in JavaScript in Plain English

New JavaScript and Web Development content every day. Follow to join our 3.5M+ monthly readers.