Search code examples
typescriptnamespacesmapped-types

How to constrain type of object keys to namespace constants?


I would like to define a mapped type whose keys are the values of all constants under a namespace.

I wasn't able to find other questions that cover this. This question and its duplicate talk about JavaScript, whereas I'm looking to strongly type the property of a class. I also couldn't find a reference in the Typescript handbook.

Description:

I have a namespace as such:

export namespace Controls {
    export const Foo = "foo";
    export const Bar = "bar";
    export const Baz = "baz";
    // ... a dozen others
}

This namespace contains nothing else. Only those exported consts.

I would like to define an object type to express the following meaning: "this object keys can only be const values declared in that namespace". Naively something like:

type Namespaced = { [K in Controls]?: ControlDelegate }

The above doesn't compile because I can't use a namespace as a type. Indexing the type also doesn't work for the same reason:

type NamespaceKeys = Controls[keyof typeof Controls]

Then I had this epiphany to use:

{ [K in keyof typeof Controls]?: ControlDelegate }

which does compile, and the resolved type looks like what I want, however I'm then unable to instantiate literals:

this.controlDelegates = {
            [Controls.Foo]: new FooControlDelegate() // it implements ControlDelegate
        }

with the following compile error:

Type '{ "foo": FooControlDelegate; }' is not assignable to type '{ readonly Foo?: ControlDelegate; ... Object literal may only specify known properties

What's the correct way to constrain the type of object keys to the values under a certain namespace?


Solution

  • keyof typeof Controls gives you the keys of Controls, which are "Foo", "Bar", "Baz". What you want are the values of Controls which are "foo", "bar", "baz" (lowercase).

    You can achieve this with typeof Controls[keyof typeof Controls].