Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript Object.assign confusion

Tags:

typescript

When I look at the signature of Object.assign I see that it uses intersection types, but the behavior is not what I expected.

I want to use Object.assign to merge some changes with an old object to create a new object (as is common practice in JS when following immutable data patterns) and have TypeScript validate that the parts and the result are correct, but it seems TypeScript allows anything through without an error.

For example:

interface Thing {
    name: string;
    age: number;
    fav: boolean;
}

let oldThing: Thing = {
    name: "Aaa",
    age: 123,
    fav: false
};

let newThing: Thing = Object.assign({}, oldThing, {
    name: "Bbb",
    age: "abc", // should be type error
    fake: "fakey" // should be unknown prop error
});

Here it is in the playground.

Why does Object.assign allow this incorrect assignment to Thing? Is there a way to make this work close to what I expected?

like image 647
Aaron Beall Avatar asked Oct 06 '16 21:10

Aaron Beall


2 Answers

The reason you intuitively expect an error here is that the intersection type constructed for the return value of Object.assign is absurd, and cannot be occupied by any real values. However, TypeScript has no way of verifying this. The return value from Object.assign here is typed as the following:

{
    name: string,
    age: string & number,
    fav: boolean,
    fake: string
}

Of course there is no actual value that is both string & number, but the compiler doesn't know that. It happily constructs this absurd type, then when you try to assign it to Thing verifies that the type of each property in Thing is satisfied by the corresponding property in the return value. Since this is the case (each of name, age and fav are present, and satisfy at least the required interface in Thing), the assignment succeeds.

If you allowed the type of newThing to be inferred, then tried assigning anything to the age property, you'd see the problem:

let newThing = Object.assign({}, oldThing, {
    name: "Bbb",
    age: "abc",
    fake: "fakey"
});
newThing.age = 10;   // Compiler error because 10 is not a string
newThing.age = "10"; // Compiler error because "10" is not a number
like image 137
Asad Saeeduddin Avatar answered Nov 18 '22 14:11

Asad Saeeduddin


You can force TypeScript to check using type assertions: i.e., as Thing or <Thing>

interface Thing {
    name: string;
    age: number;
    fav?: boolean;  // You may need to mark this as optional depending on
                    // your requirement, otherwise TypeScript will 
                    // generate missing prop error
}

//...

let newThing: Thing = Object.assign({}, oldThing, {
    name: "Bbb",
    age: "abc",     // will cause type error
    fake: "fakey"   // howerver, no error for unknown prop
} as Thing);
like image 38
engineforce Avatar answered Nov 18 '22 15:11

engineforce