JavaScript Development Space

Applying the Open/Closed Principle in TypeScript

When working with functions or methods that accept an object and its property as a string, it can be challenging to maintain type safety as objects evolve. Here's how to create a utility type in TypeScript to ensure that property names are type-checked at compile time.

Problem

Consider this function call:

ts
1 updateDate(user, "date");

If the property changes (user.date → user.birthday), the compiler won’t catch the mismatch, leading to potential bugs during runtime.

Solution

We can use a custom utility type, ValidPath, to ensure type safety for object properties.

Step 1: Define the Type

The ValidPath type recursively generates string representations of all object keys, including nested properties:

ts
1 type PropertiesOnly<T> = Pick<
2 T,
3 {
4 [K in keyof T]: T[K] extends (...args: any[]) => any ? never : K;
5 }[keyof T]
6 >;
7
8 type IsArray<T> = T extends (infer U)[] ? true : false;
9
10 export type ValidPath<T, Prefix extends string = ""> = T extends object
11 ? {
12 [K in keyof PropertiesOnly<T> & (string | number)]: IsArray<
13 T[K]
14 > extends true
15 ? never
16 : `${K}` | `${K}.${ValidPath<T[K], `${Prefix}${K}.`>}`;
17 }[keyof PropertiesOnly<T> & string]
18 : never;

Explanation:

  • Exclude Functions: PropertiesOnly filters out methods from the object.
  • Handle Arrays: IsArray excludes arrays from the recursion to avoid complex union types.
  • Recursive Type: The ValidPath type generates all possible key paths in the object, including nested properties.

Step 2: Create a Utility Function

The validatePath function ensures that only valid property paths are accepted.

ts
1 export function validatePath<T>(path: ValidPath<T>): ValidPath<T> {
2 return path; // All checks occur during compilation
3 }

Example Usage

Given this object:

ts
1 const data = {
2 user: {
3 name: "Alice",
4 details: { age: 30 },
5 },
6 };

You can validate property paths like this:

ts
1 validatePath<typeof data>("user.name"); // Valid
2 validatePath<typeof data>("user.details.age"); // Valid
3 validatePath<typeof data>("user.invalid"); // Error

Additional Example

For more advanced scenarios:

ts
1 function floatHandler<T>(
2 value: string,
3 row: any,
4 property: ValidPath<T>,
5 propertyRaw: string
6 ) {
7 // Your code here
8 }
9
10 floatHandler<(typeof data)["user"]>("42", data.user, "details.age", "age");

This approach ensures type safety and prevents runtime errors caused by invalid property names.

JavaScript Development Space

JSDev Space – Your go-to hub for JavaScript development. Explore expert guides, best practices, and the latest trends in web development, React, Node.js, and more. Stay ahead with cutting-edge tutorials, tools, and insights for modern JS developers. 🚀

Join our growing community of developers! Follow us on social media for updates, coding tips, and exclusive content. Stay connected and level up your JavaScript skills with us! 🔥

© 2025 JavaScript Development Space - Master JS and NodeJS. All rights reserved.