Is it possible to type an array of strings in such a way that the array can only be a valid property path in a given object? The type definition should work for all deeply nested objects.
Example:
const object1 = {
someProperty: true
};
const object2 = {
nestedObject: object1,
anotherProperty: 2
};
type PropertyPath<Type extends object> = [keyof Type, ...Array<string>]; // <-- this needs to be improved
// ----------------------------------------------------------------
let propertyPath1: PropertyPath<typeof object1>;
propertyPath1 = ["someProperty"]; // works
propertyPath1 = ["doesntExist"]; // should not work
let propertyPath2: PropertyPath<typeof object2>;
propertyPath2 = ["nestedObject", "someProperty"]; // works
propertyPath2 = ["nestedObject", "doesntExist"]; // should not work
propertyPath2 = ["doesntExist"]; // should not work
In the answer to the question this duplicates you can use the recursive Paths<>
or Leaves<>
type aliases, depending on whether or not you want to support all paths that start at the root and end anywhere in the tree (Paths<>
) or if you want to only support paths that start at the root and end at the leaves of the tree (Leaves<>
):
type Paths<T extends object> = {
[K in keyof T]: T[K] extends object
? [K, ...Paths<T[K]>] : [K]
}[keyof T];
type AllPathsObject2 = Paths<typeof object2>;
// type AllPathsObject2 = ["nestedObject"] | ["nestedObject", "someProperty"] |
// ["anotherProperty"]
type LeavesObject2 = Leaves<typeof object2>;
// type LeavesObject2 = ["nestedObject", "someProperty"] | ["anotherProperty"]
I'll assume it's Paths
but you can change it to Leaves
if that fits your use case. Here's the behavior you get, which matches what you asked for:
let propertyPath1: Paths<typeof object1>;
propertyPath1 = ["someProperty"]; // works
propertyPath1 = ["doesntExist"]; // error!
// ~~~~~~~~~~~~~~
let propertyPath2: Paths<typeof object2>;
propertyPath2 = ["nestedObject", "someProperty"]; // works
propertyPath2 = ["nestedObject", "doesntExist"]; // error!
// ~~~~~~~~~~~~~
propertyPath2 = ["doesntExist"]; // error!
// ~~~~~~~~~~~~~
Okay, hope that helps; good luck!