Aaron Beall
Aaron Beall

Reputation: 52133

Type assertion vs properties

I've noticed the following behavior:

interface User {
    name: string;
    age: number;
}

let user:User = {
    name: "Aaron",
    age: 99
}

If I omit age: 99 I get a compile error. This is the behavior I expect.

However:

function putUser(user:User):void { }

putUser(<User> {
    name: "Aaron"
});

I get no compile error for missing non-optional property age.

In fact in my real code-base I've found that with casting type assertion I can at times add completely wrong properties to the object literal, but I wasn't able to reproduce this behavior in a simple case like above.

Can anyone explain what's going on here?


Edit: Omitting the assertion works, thanks @Amid. However, where I ran into this as an issue was in this kind scenario:

interface User { name: string; }

function putUser(user:User):void { }

interface Musician extends User { instrument: string; }

putUser(<Musician> {
    name: "Bob Dylan"
});

The issue is that I want to validate the Musician object, but putUser only expects the base User object. I thought the type assertion would give me better type safety, but in this case it actually reduces type safety.


Edit #2

It looks like I can get what I wanted using a generic type constraint, which actually looks an awful lot like my type assertion:

function putUser<T extends User>(user:T):void { }

putUser<Musician>({
    name: "Bob Dylan",
    instrument: "Guitar"
});

In this case it seems it will make sure the object literal is exactly an implementation of Musician.

Upvotes: 2

Views: 1553

Answers (2)

Amid
Amid

Reputation: 22352

The reason is that you are using type assertion here. That basically overrides all typescript type checks. If you remove < User > part - compiler will give you error you expect

EDIT:

This is what you want:

interface User { name: string; }

function putUser<T extends User>(user:T):void { }

interface Musician extends User { instrument: string; }

putUser<Musician>({
    name: "Bob Dylan"
});

Upvotes: 3

shadeglare
shadeglare

Reputation: 7536

You made a type assertion. You would prefer to use type assertions when you communicate with external systems (like requesting API) when you have the shape of data you're going to receive but no own source of it.

The typescript type assertion are quite smart. It won't allow you to make completely wrong things. Here's an example:

interface Point {
    x: number;
    y: number;
}

function process(box: Point) {}

let empty = {};
let part = { x: 3 };
let full = { x: 3, y: 5 };
let vague = { x: 3, y: 5, z: 7 };

process(empty); //type mismatch, got {} expected {x, y} 
process(part); //type mismatch, only {x} expected {x, y}
process(null); //no type mismatch, argument can be null
process(full); //full match
process(vague); //still works, but the function is not aware of "z" field.

process(<Point>empty); //assertion valid, object can be a Point instance
process(<Point>"123"); //wrong assertion, expected object instead of string
process(<Point>[]); //wrong assertion, expected object instead of array 

Upvotes: 2

Related Questions