Context
I am trying to create a type-safe array of path segments to drill into an object. There are only 2 levels of depth to this particular interface that I'm trying to build the types against. I'll eventually use these segments to index into an object using dot notation, but for now, I'm just trying to make sure the types are constrained enough so that incorrect paths can't be added.
Example
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"]; // good - this errors so we're catching bad paths
const allowsCorrectPath: ExamplePath = ["days", "monday"]; // good - valid paths are accepted
const allowsIncorrectPaths: ExamplePath = ["weekend", "monday"]; // bad! - invalid combinations of paths are allowed
The types I've come up with so far are too loose, allowing for any permutation of path segments, even if those are impossible (i.e. ["weekend", "monday"]
). I've tried to use a generic type variable with tuple types, by using the first path segment as type T
to index into the Example
type, before getting the keys of that.
The resulting type of this index approach is a union of:
(Days | Weekend | string)
Using keyof
on this union type, resulted in the error
Type 'string' is not assignable to type 'never'.ts(2322)
So instead, a conditional type was used KeysOfUnions
to fetch the keys of each union member, which resulted in overly loose typing as you can imagine.
Question
How can I infer the second element (path segment) of the tuple using the first element, ensuring that the type system enforces that only valid combinations of path segments can be added?
Edit 1: I'm also looking for a solution that allows for single segments if there are no more properties left to drill into. i.e. ["year"]
, ideally where the addition of any more elements to the array would break the types.
Edit 2: A maybe not so small addendum the example given was a fictional interface with 2 levels of nesting, however, I simplified its structure too much in my question, the actual interface has roughly 5 levels of nesting. For example, let's say that those Days
and Weekend
example interfaces were much deeper, with each day containing child objects and so on. I actually intended for a solution to type a tuple / array drilling down only for 2 levels of properties, ignoring deeper path segments. So maybe recursive approaches are out of the question for this constraint.