In Typescript, why is it that an object cannot specify excess properties on assignment but it can when passed to a function, using same Interface?

Viewed 346

I've added the code that gives me the error to TypeScript's playground, you'll see the error at the object assignment at the bottom.

interface Car {
    make: string,
    model: string,
    year: number
}

function repairCar(car: Car) {

}

let myCar = {
    make: 'Honda',
    model: 'Accord',
    year: 1992,
    color: 'red'
}

repairCar(myCar);

But when I add this code:

let myCar2: Car = {
    make: 'Honda',
    model: 'Accord',
    year: 1992,
    color: 'red'
}

I get this error:

Type '{ make: string; model: string; year: number; color: string; }' is not assignable to type 'Car'. Object literal may only specify known properties, and 'color' does not exist in type 'Car'.

I don't understand why it is allowed to have the excess property on the object that gets passed to the repairCar function but not at the time when it gets assigned to a variable that has a type annotation, it's the same interface.

3 Answers

Imagine it would work without an error:

  let myCar2: Car = {
    make: 'Honda',
    model: 'Accord',
    year: 1992,
    color: 'red'
 };

Then the color property is completely senseless as:

 console.log(myCar2.color);

won't work as color is not part of the Car type.

why is it [then] allowed to have the excess property on the object that gets passed to the repairCar function?

Because you can pass down an object with additional properties to a function without any side effects, and still use the color property elsewhere:

 repairCar(myCar);
 console.log(myCar.color); 

Example from the real world: It doesnt make sense to call a bird an animal, because if it is only an animal it couldn't fly, thats why we call birds birds and not animals. However if you say that an animal can move, a bird can also move as it is an animal.


If you want to resolve the error just make the color optional, or use a supertype:

 interface ColoredCar extends Car {
   color: string;
 }

 // or

 interface Car {
  make: string,
  model: string,
  year: number
  color?: string;
 }

Objects in Typescript are only allowed to have the fields specified in the type. Unlike other languages we can not define a variable to store a subclass. Hence, we get an error writing

interface Car = {
    make: string,
    model: string,
    year: number
}

let myCar: Car = {
    make: 'Honda',
    model: 'Accord',
    year: 1992,
    color: 'red'
}

However, Typescript allows us to call methods with objects that have all the fields required by the method. Allowing this will not break the method since the subclass must implement all fields required by the method. We can therefore write

interface ColoredCar extends Card {
    color: string
}

let myColoredCar: ColoredCar = {
    make: 'Honda',
    model: 'Accord',
    year: 1992,
    color: 'red'
}

function repairCar(car: Car) {
     // Implementation...
}

// Doesn't matter for the method repairCar if myColoredCar is a ColoredCar or a Car as 
// long as make, model and year are defined
repairCar(myColoredCar);

Creating a typed object is validated at compile time. That part is normal for any static typed language. It is doing implicit casting for the dynamic obj and it is successful because of containing necessary fields. But it doesnt have to be a Car typed object.

Related