Background
In my current project, I'm attempting to create a secure array of path segments for navigating through an object. The interface I'm developing is specifically designed to handle objects with only two levels of depth. Eventually, these segments will be used to access properties within an object using dot notation. At this stage, my main focus is on ensuring that only valid paths can be added by enforcing strict type constraints.
Illustration
interface Days {
monday: string;
tueday: string;
wednesday: string;
thursday: string;
friday: string;
saturday: string;
sunday: string;
}
interface Weekend {
saturday: string;
sunday: string;
}
interface Example {
days: Days;
weekend: Weekend;
year: string;
}
type KeysOfUnions<T> = T extends T ? keyof T : never;
type ExamplePath<T extends keyof Example = keyof Example> = [T, KeysOfUnions<Example[T]>?];
const correctlyErrors: ExamplePath = ["days", "test"]; // this correctly triggers an error for invalid paths
const allowsCorrectPath: ExamplePath = ["days", "monday"]; // this accepts valid paths
const allowsIncorrectPaths: ExamplePath = ["weekend", "monday"]; // this should not be allowed as it is an invalid path
My current type definitions are not strict enough, as they permit any combination of path segments, including those that are impossible (such as
["weekend", "monday"]
). I've experimented with using a generic type variable with tuple types, where the first path segment serves as the type T
for indexing into the Example
type before retrieving its keys.
This indexing approach results in a union type of:
(Days | Weekend | string)
When applying keyof
to this union type, the following error arises:
Type 'string' is not assignable to type 'never'.ts(2322)
Therefore, I resorted to using a conditional type KeysOfUnions
to extract the keys of each union member, leading to overly lenient typing.
Query
How can I automatically deduce the second element (path segment) of the tuple based on the first element, and ensure that only valid combinations of path segments can be included in the array?
Edit 1: I also require a solution that accommodates single segments when there are no additional properties to traverse. For instance, ["year"]
, whereby the introduction of more elements would result in type errors.
Edit 2: An important note 😅 the provided example portrayed a fictitious interface with two levels of nesting; however, I oversimplified its structure in my inquiry. The actual interface has approximately five levels of nesting. For example, suppose the interfaces Days
and Weekend
were significantly more complex, with each day containing child objects. My objective is to develop a solution that navigates only two levels deep, disregarding deeper path segments. Therefore, recursive methods may not be viable within this constraint.