I have an object like this:
const routes = {
home: { path: '/', page: 'home' },
profile: { path: '/profile', page: 'users/profile' }
}
I would like to define a derived type from this, like so:
type RouteName = keyof typeof routes
, which creates a type like "home" | "profile"
.
However, I can't then do:
for (let name in routes) {
router.add({ name, ...routes[name]})
}
because the compiler complains about routes[name]
being of implicit type any
:
Element implicitly has an 'any' type because type '{ home: { path: string; page: string; }; profile: { path: string; page: string; };' has no index signature.
If I modify the definition of routes to:
interface RouteDefinition {
path: string
page: string
}
const routes: {[key: string]: RouteDefinition} = {
home: { path: '/', page: 'home' },
profile: { path: '/profile', page: 'users/profile' }
}
the generated type type RouteName = keyof typeof routes
is now string
instead of "home"|"profile"
.
I could of course define a hardcoded RouteName
type, but in case it isn't clear, I'm trying to avoid having to define the route names in two places, especially when the keys of the object strictly define the set of possibilities.
The object only needs to be defined once, and never needs to be reassigned. I've tried a bunch of combinations of Readonly<>
, casting etc, but can't figure it out. Is there a way to do this?
(I'm using Typescript 2.8.1)
TypeScript doesn't consider it safe to assume that for..in
keys are exactly the keys defined in the type, because in JavaScript all objects are open.
You can use an assertion to make the compile error go away:
for (let name in routes) {
routes[name as RouteName]; // no error
}
Or, what I would do is combine your two approaches. You can define your routes
as you are doing, extract the keys as a RouteName
, but also make a RouteDefinition
and assign your routes to an indexed type (which could be done using an assertion to a new variable, or a function parameter) when you want to map over them:
interface RouteDefinition {
path: string;
page: string;
}
const routes = {
home: { path: '/', page: 'home' },
profile: { path: '/profile', page: 'users/profile' }
}
type RouteName = keyof typeof routes;
mapRoutes(routes);
function mapRoutes(routes: { [key: string]: RouteDefinition }) {
for (let name in routes) {
routes[name] // no error
}
}
If your routes
literal doesn't satisfy the RouteDefinition
(missing a key, key of wrong type) then you'll get an error at the assignment site, ie mapRoutes(routes)
above.