Use argument’s property as type in typescript

I want to assign type to property of class that depends on passed property in argument. I wonder if it’s possible.

Example:

enum MyEnum {
    g,
    f,
    h,
}

interface MappedTypes {
    [MyEnum.f]: X
    [MyEnum.g]: Y
    [MyEnum.h]: Z
}

class MyClass {
    a: string // does not matter
    b: something like MappedTypes[c]
    c: MyEnum

    constructor(params: { a: string; b: MappedTypes[<inherit from c>]; c: MyType }) {
        // for example
        // if c === MyEnum.g
        // b must be Y as mapped in MappedTypes  
    }
}

Answer

One thing you can do is make your class generic in E, the type of the c property, and give b a lookup type like MappedTypes[E]. This is the closest I can get to the code you’re writing now:

class MyClass<E extends MyEnum> {
    a: string
    b: MappedTypes[E]
    c: E
    constructor(params: { a: string; b: MappedTypes[E]; c: E }) {
        this.a = params.a;
        this.b = params.b;
        this.c = params.c
    }
}

Beware though that once you use a generic like this you might find it difficult inside the implementation of MyClass to use b and c in a correlated way. See microsoft/TypeScript#13995 and microsoft/TypeScript#24085 for more information.


Another way to proceed here is to keep b and c bundled into a single params property of a discriminated union type, like this:

type Params = { [K in MyEnum]: { a: string, b: MappedTypes[K], c: K } }[MyEnum]

If you examine Params you’ll see that it evaluates to the following union:

/* 
type Params = 
  { a: string; b: Y; c: MyEnum.g; } | 
  { a: string; b: X; c: MyEnum.f; } | 
  { a: string; b: Z; c: MyEnum.h; }
*/

And then you could define MyClass like this:

class MyClassU {
    constructor(public params: Params) { }
}

So instead of this.b you’d need to refer to this.params.b, which might be annoying. On the plus side, you will be able to do things like switch on this.params.c and the compiler will understand that it has an implication for the type of this.params.b:

    someMethod() {
        switch (this.params.c) {
            case (MyEnum.f): {
                this.params.b; // known to be X
            }
        }
    }

Playground link to code

Leave a Reply

Your email address will not be published. Required fields are marked *