Search code examples
typescripttypescript-typings

TypeScript type cast


I have an object, and I need to add some extra properties to it in some cases.

interface Item {
    name: string
}

function addProp(obj: Item) {
    type WithFoo = Item & { foo?: string; }

    // The following does NOT work
    obj = obj as WithFoo;

    if (obj.name == 'test') {
        obj.foo = 'hello';
     }
}

It seems obj = obj as AnotherType does NOT work, but if I assign to another variable, it works, const anotherObj = obj as AnotherType.

Is there any way to cast the type, without introducing another variable?

Here is online playground


Solution

  • 1.) Why does obj = obj as WithFoo not work? Is there any way to cast the type, without introducing another variable?

    First of all the obj function parameter variable is declared with an Item type, so TS does not know that obj contains the type { foo?: string}. Your second chance would be the compiler's control flow analysis for assignments, which in this case cannot narrow the obj type to WithFoo, as obj does not have a union type:

    An assignment (including an initializer in a declaration) of a value of type S to a variable of type T changes the type of that variable to T narrowed by S in the code path that follows the assignment.

    The type T narrowed by S is computed as follows:

    If T is not a union type, the result is T.
    If T is a union type, the result is the union of each constituent type in T to which S is assignable.

    That is the reason, why you get the error Property 'foo' does not exist on type 'Item' in the example. Instead the following code would properly narrow and compile:

    type WithFoo = { foo?: string; }
    
    function addProp(obj: Item | WithFoo) {
        obj = obj as WithFoo;
        obj.foo = 'hello';
    }
    

    If you don't want to do re-assignments or introduce another variable, foo property can be accessed with an inline type assertion, which in general is usable on all JavaScript expressions:

    (obj as WithFoo).foo = 'hello';
    

    Having said that, a more safe way is probably to assume obj to be a union type Item | WithFoo and use type guards instead of a hard cast (see @Maciej Sikora's answer).

    2.) Why does const anotherObj = obj as AnotherType work?

    When you declare a new variable like const anotherObj = obj as AnotherType, the compiler automatically infers the type of variable anotherObj to AnotherType. TS does additional checks to make sure, AnotherType is compatible to typeof obj. E.g. this wouldn't compile:

    function addProp(obj: Item) {
        const anotherObj = obj as string // error (OK)
        // ...
    }
    

    3.) Can a variable's type be changed after its declaration?

    Nope, let and const variables cannot be redeclared (var with same type, but not important here), which implies, the declared variable types also cannot be changed. The variables could be narrowed via control flow analysis though, see 1.).