Search code examples
typescriptundefinedobject-literal

Assigning to an object literal dynamically in Typescript


I have been at this for hours and cannot determine why I am getting this typescript error.

I can't show the actual code, but I've reduced the problem down to the following example code:

    abstract class CustomerType {
        protected abstract customerInfo?: IUserInfo;
    }
    
    enum CustomerGroup {
        FirstTimer = "first timer",
        Subscriber = "subscriber"
    }
    
    interface IUserInfo {
        group?: CustomerGroup;
        firstClassMember?: boolean;
        rewardsMember?: boolean;
    }
    
    abstract class CustomerType {
        protected abstract customerInfo?: IUserInfo;
    }
    
    class Default extends CustomerType {
        protected customerInfo: IUserInfo = {};
        constructor(){
            super();
        }
        
        get _customerInfo(){
            return this.customerInfo ?? {}
        }
        
        set _customerInfo(customerInput: IUserInfo){
            let input: keyof IUserInfo;
            for(input in customerInput){
                if(customerInput[input] === undefined) continue;
                    this.customerInfo = customerInput[input];
            }
        }
    }

Here is the Typescript error:

Type 'boolean | CustomerGroup | undefined' is not assignable to type 'IUserInfo'. Type 'undefined' is not assignable to type 'IUserInfo'.(2322)

Typescript cannot infer that the customerInput[input] is not undefined, despite the check right above the error.

Here is a link to the typescript playground: https://www.typescriptlang.org/play?#code/FAUwdgrgtgBAwhAzgFwPZRAJwOKdRABxgG9gZyYAxAS0xQBVqNMYBeGAIgDNaUZkmWDgBoyFAMoQARogDGmalKxtOiaYgVLMHYAF9gwamGRYuAQ1kgYASQCqiLNbBdUJMQHM8hAPwAueEhozLj4BADcYjx0yHAANmaIiACyIFBafjBSqKixIGZgEeSYIADuZpgAJsmp6f5ZOXkFegbAZjLImBbIMLLxiQEo6Fj0AJ4EVqTkBHgmsiYVMG0onXM9gUOYTi4Zdg6bzqgR+sC9CYgwACIg5hCx3SAAHiZgVQNBw2MTwNOos-Nrg2YW1Q-l2jgOKmIukKFFkqDAywgc1QmAAFABKSYUbFqcZo9Ew8jHbHuEDdAD6snWQIOGKx2KKZIgmDA-AAFtREAA6KmA8EuGDebwkfTY4kUBwU3nvfYuVHSjZOAgQZCg+z81CYsQMmC5bpGZWqmAAaxAI1QXBs6tlh21DJcaINKpgRgBMqVKq1Op11C48up4MNAG0ncgALpsVjsCAva5GEAVdE9eECSAgQne8jIDnchU0gXsPOBlUhsCGsMZsV2-S6IA

Here is a link to a codepen: https://codepen.io/darylshy/pen/YzjWBoG?editors=0011


Solution

  • The correct code should be:

    set _customerInfo(customerInput: IUserInfo){
       let input: keyof IUserInfo;
        for(input in customerInput){
            if(customerInput[input] === undefined) continue;
            this.customerInfo[input] = customerInput[input];
        }
    }
    

    Not sure why TS is complaining at first glance to be honest.

    It would be better written like so anyway:

    set _customerInfo(customerInput: IUserInfo){
        Object.assign(this.customerInfo, customerInput)
    }
    

    Your loop didn't make much sense: you were overwriting this.customerInfo, which is a collection of values, with a single value. That was your mistake.

    I'd also like to add that if you don't understand what TS is telling you, you can always try to run the code and inspect the return value. You should be writing tests anyway, why not write them as you code? It will get you unstuck much sooner.

    For instance if you had written the following test

    const a = new Default();
    
    a._customerInfo = { rewardsMember: true };
    
    t.deepEqual(a._customerInfo, { rewardsMember: true })
    

    The test runner would have told you that a._customerInfo is true instead of { rewardsMember: true }