James
James

Reputation: 923

Why does TypeScript allow excess properties on objects only when defined with curly braces?

When defining interfaces, the TypeScript documentation mentions that as long as an object takes the shape of the interface any excess object properties are allowed.

An example

interface Person {
    name: string
}

function print(somebody: Person) {
    console.log(somebody.name);
}

let obj = { name: "Johnny", additionalProps: true }

print(obj); // this is okay

But is this only true for function parameters? Below I try to create an object cast as a specific type, and adding additional properties throws errors only when I don't use curly braces.

interface Animal {
    name: string;
}

let myDog = <Animal> { 
    name: "Spot",
    altProperty: "anything" // no error
};

myDog.altProperty = "anything else"; // Property 'altProperty' does not exist on type 'Animal'

It seems you can assign as many properties as you like to an object when asserting its type, but you cannot access any of these because they are not in the type definition. Why is this?

Upvotes: 0

Views: 669

Answers (2)

GreeneCreations
GreeneCreations

Reputation: 1242

The reason is because TypeScript's type system is based on structural subtyping which I think can be summarized best by saying that an object corresponds to a type if it has the attributes of that type. Some people joke that it is just "duck typing" (i.e. if it quacks like a duck then it is a duck).

So in your example it actually has nothing to do with being declared with an interface vs simply curly braces.

interface Person {
    name: string
}
interface PersonPlusMore {
    name: string,
    age: number
}

function print(somebody: Person) {
    console.log(somebody.name);
}

let obj : PersonPlusMore = { name: "Johnny", age: 30 }; // <-- notice that we used explicit typing

print(obj); // this is still okay because it has "at least" the properties that your print function requires

In this manner, TypeScript keeps the flexibility of JavaScript but still makes sure that functions have the data that they require to process the information properly at runtime.

Upvotes: 0

Rob
Rob

Reputation: 27367

Interfaces in typescript merely provide compile-time checks explaining what members are available on the object.

Your code here:

let myDog = <Animal> 

Is saying "I have some object, but I want to only expose the members defined by the Animal interface". You've explicitly told the compiler to give you an error when ever you reference a member not defined in Animal.

You're able to reference altProperty when creating the object, because you haven't given it a type yet. However, were you to write:

let myDog: Animal = { 
    //name: "Spot",
    altProperty: "anything" // no error
};

You would get an error for attempting to cast an invalid object to Animal

Now, you don't need to cast the object to Animal to be able to use it as such. You could write:

interface Animal {
    name: string;
}

let myDog = { 
    name: "Spot",
    altProperty: "anything"
};

myDog.altProperty = "anything else";

doSomething(myDog);
function doSomething(object: Animal) {}

And it would work fine. In fact, the only reason to explicitly type a variable like you've done would be to deliberately catch the error you're experiencing.

Upvotes: 1

Related Questions