I want to implement a basic utility function that can extract a specific path from an object like the following:
interface Human {
address: {
city: {
name: string;
}
}
}
const human: Human = { address: { city: { name: "Town"}}};
getIn<Human>(human, "address.city.name"); // Returns "Town"
In JavaScript, creating this helper is straightforward, but ensuring type safety in TypeScript adds complexity. I have managed to make some progress:
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, ...0[]];
type Join<K, P> = K extends string | number
? P extends string | number
? `${K}${"" extends P ? "" : "."}${P}`
: never
: never;
type Path<T, D extends number = 4> = [D] extends [never]
? never
: T extends object
? {
[K in keyof T]-?: K extends string | number
? `${K}` | Join<K, Path<T[K], Prev[D]>>
: never;
}[keyof T]
: "";
function getIn<T extends Record<string, any>>(object: T, path: Path<T>): T {
const parts = path.split(".");
return parts.reduce<T>((result, key) => {
if (result !== undefined && result[key]) {
return result[key];
}
return undefined;
}, object);
}
This implementation functions correctly, but the issue lies in the fact that the return type of getIn
should not solely be T
, but rather a specific property within T
determined by the provided path. For example, calling the function as follows:
getIn<Human>(human, "address.city.name"); // Returns "Town"
TypeScript should infer that the return value is a string, based on the definition in the Human
interface. Similarly, for "address.city"
, the return type should be City
, and so on.
Is there a method to achieve this level of type safety?