Why does the following assertion work:
interface AllRequired {
a: string;
b: string;
}
let all = {a: "foo"} as AllRequired; // No error
But this assertion gives an error:
interface SomeOptional {
a?: string;
b: string;
}
let some = {a: "foo"} as SomeOptional; // Error: Property 'b' missing
The only difference I can see is making one of the interface properties optional (?
). It seems that if all properties are not optional, I can assert a partial object to the interface, but as soon as any of the interface properties are optional, I cannot assert a partial object anymore. This doesn't really make sense to me and I've been unable to find an explanation of this behavior. What's going on here?
For context: I encountered this behavior while trying to work around the problem that React's setState()
takes a partial state object, but TypeScript doesn't yet have partial types to make this work properly with your state interface. As a workaround I came up with setState({a: "a"} as MyState)
and found this works as long as interface MyState
fields are all non-optional, but fails as soon as some properties are optional. (Making all properties optional is a workaround, but very undesirable in my case. )
Type assertions can only be used to convert between a type and a subtype of it.
Let's say you declared the following variables:
declare var foo: number | string;
declare var bar: number;
Note number
is a subtype of number | string
, meaning any value that matches the type number
(e.g. 3
) also matches number | string
. Therefore, it is allowed to use type assertions to convert between these types:
bar = foo as number; /* convert to subtype */
foo = bar as number | string; /* convert to supertype (assertion not necessary but allowed) */
Similarly, { a: string, b: string }
is a subtype of { a: string }
. Any value that matches { a: string, b: string }
(e.g. { a: "A", b: "B" }
) also matches { a: string }
, because it has an a
property of type string
.
In contrast, neither of { a?: string, b: string }
or { a: string }
is a subtype of the other.
Some values (e.g. { b: "B" }
) match only the former, and others (e.g. { a: "A" }
) match only the latter.
If you really need to convert between unrelated types, you can work around this by using a common supertype (such as any
) as an intermediate:
let foo = ({ a: "A" } as any) as { a?: string, b: string };
The relevant part in the spec is 4.16 Type Assertions:
In a type assertion expression of the form < T > e, e is contextually typed (section 4.23) by T and the resulting type of e is required to be assignable to T, or T is required to be assignable to the widened form of the resulting type of e, or otherwise a compile-time error occurs.
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