Raf
Raf

Reputation: 33

Cannot dynamically set interface object's property values

I have the following code:

interface MyType {
    a: string;
    b: number;
}

class SomeClass {
    // ... more things going on

    beta: MyType = {a: 'Lorem', b: 3};

    myFunction(alpha: MyType) {
        // beta is an object of type MyType
        for (const key in alpha) {
            this.beta[key as keyof typeof this.beta] = alpha[key as keyof typeof alpha] ?? '';
        }
    }
}

see identical playground here.

However, this produces the error:

Element implicitly has an 'any' type because expression of type 'string | number | symbol' can't be used to index type 'MyType'.
  No index signature with a parameter of type 'string' was found on type 'MyType'.

I've tried playing around with it for a long time, tweaking it, and it still doesn't work -- I've no idea why. (I'm using keyof typeof this.beta for simplicity but keyof MyType doesn't work either).

Thanks for the help in advance.

Upvotes: 1

Views: 151

Answers (1)

I believe that this question is very similar to yours.

Consider this example:

interface MyType {
    a: string;
    b: number;
}

class SomeClass {
    beta: MyType = { a: 'Lorem', b: 3 };

    myFunction(alpha: MyType) {
        // beta is an object of type MyType
        for (const key in alpha) {
            const x = this.beta[key as keyof MyType]   // string | number
            const y = alpha[key as keyof typeof alpha] // number | number

            this.beta[key as keyof MyType] = alpha[key as keyof typeof alpha] ?? ''; // expected error
        }
    }
}

As you see, x and y can be a string or number.

declare var x: string | number;
x = 42;
x = 'str'

Since TS is about static type checking, it is unable to figure out whether this.beta[key] has same type as alpha[keys], they both are string | number.

You are getting Type 'string | number' is not assignable to type 'never'

This is because objects are contravariant in their key types. And candidates for the same type variable in contra-variant positions causes an intersection type to be inferred. Hence string & number gives never.

My advise - don't mutate values in typescript.

You can find more explanations and examples in my blog

How to fix it?

interface MyType {
    a: string;
    b: number;
}

class SomeClass {
    beta: MyType = { a: 'Lorem', b: 3 };

    myFunction(alpha: MyType) {
        const result = (Object.keys(alpha) as Array<keyof MyType>).reduce<MyType>((acc, elem) => ({
            ...acc,
            [elem]: alpha[elem]
        }), this.beta)

        this.beta = result
    }
}

Upvotes: 1

Related Questions