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?
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
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With