I have a use case where I need to extract all possible paths to nested fields inside a TypeScript interface
.
When I define a property within the "main" type that has nested and optional properties, then it works as expected. However, I am running into an issue with an optional property that points to another interface.
type NestedKeyOf<T extends object> = {
[Key in keyof T & (string | number)]: T[Key] extends object
? `${Key}` | `${Key}.${NestedKeyOf<T[Key]>}`
: `${Key}`;
}[keyof T & (string | number)];
interface MyParameters {
Foo: string;
Bar?: string;
}
interface MyObject {
Parameters?: MyParameters;
Foo: {
Bar?: string;
}
}
// Here, I'd expect "Parameters.Foo" and "Parameters.Bar" to also exist
// Instead, all I get is "Parameters" | "Foo" | "Foo.Bar"
type Path = NestedKeyOf<MyObject> // "Parameters" | "Foo" | "Foo.Bar"
Could someone please explain what is the reason for this behavior? Thanks!
CodePudding user response:
The problem is that type Test = MyObject["Parameters"] extends object ? true : false
evaluates to false
if Parameters is optional. You can fix it by excluding the case of it being undefined with Exclude<T[Key], undefined> extends object
instead of T[Key] extends object
(playground):
type NestedKeyOf<T extends object> = {
[Key in keyof T & (string | number)]: Exclude<T[Key], undefined> extends object
? `${Key}` | `${Key}.${NestedKeyOf<Exclude<T[Key], undefined>>}`
: `${Key}`;
}[keyof T & (string | number)];
interface MyParameters {
Foo: string;
Bar?: string;
}
interface MyObject {
Parameters?: MyParameters;
Foo: {
Bar?: string;
}
}
type Path = NestedKeyOf<MyObject>