I have a recursively typed object that I want to get the keys of and any child keys of a certain type.
For instance. Below I want to get a union type of:
'/another' | '/parent' | '/child'
Example:
export interface RouteEntry {
readonly name: string,
readonly nested : RouteList | null
}
export interface RouteList {
readonly [key : string] : RouteEntry
}
export const list : RouteList = {
'/parent': {
name: 'parentTitle',
nested: {
'/child': {
name: 'child',
nested: null,
},
},
},
'/another': {
name: 'anotherTitle',
nested: null
},
}
In typescript you can use keyof typeof RouteList to get the union type:
'/another' | '/parent'
Is there a method to also include the nested types
Best Answer
That's a tough one. TypeScript lacks
both mapped conditional types andgeneral recursive type definitions, which are both what I'd want to use to give you that union type. (Edit 2019-04-05: conditional types were introduced in TS2.8) There are some sticking points with what you want:nested
property of aRouteEntry
can sometimes benull
, and type expressions that evaluate tokeyof null
ornull[keyof null]
start to break things. One needs to be careful. My workaround involves adding a dummy key so that it's never null, and then removing it at the end.RouteListNestedKeys<X>
) seems to need to be defined in terms of itself, and you will get a "circular reference" error. A workaround would be to provide something that works up to some finite level of nesting (say, 9 levels deep). This might cause the compiler to slow way down, since it could eagerly evaluate all 9 levels instead of deferring the evaluation until later.Diff
which needs at least TypeScript 2.4 to run properly.All that means: I have a solution which works, but I warn you, it's complex and crazy. One last thing before I drop in the code: you need to change
to
That is, remove the type annotation from the
list
variable. If you specify it asRouteList
, you are throwing away TypeScript's knowledge of the exact structure oflist
, and you will get nothing butstring
as the key type. By leaving off the annotation, you let TypeScript infer the type, and therefore it will remember the entire nested structure.Okay, here goes:
Let's try it out:
If you inspect
ListNestedKeys
you will see that it is"parent" | "another" | "child"
, as you wanted. It's up to you whether that was worth it or not.Whew! Hope that helps. Good luck!