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