Typescript is not complaining about missing or too many object properties

Issue

Does anyone have an idea why Typescript is not complaining about the missing c?

type MyType = {a: number, b: number, c: number}


const Defaults = {myDefault: {a: 5, b: 6} as MyType}

If I have

const Defaults = {myDefault: {a: 5, b: 6, d: 9} as MyType}

it’s complaining that c is a missing property (not complaining d is unknown). But it’s not complaining if I have just ab or abcd.

Adding required also changes nothing.

const Defaults = {myDefault: {a: 5, b: 6} as Required<MyType>}

Why could this be?

Solution

The type assertion in TypeScript allows you to

convert to a more specific or less specific version of a type

If type MyType = {a: number, b: number, c: number} and const Defaults = {myDefault: {a: 5, b: 6} as MyType}, then for TypeScript {a: 5, b: 6} sufficiently overlapps with MyType. Its type is contained in MyType.

Now, consider Defaults = {myDefault: {a: 5, b: 6, c:4, d: 3} as MyType}, then this is also allowed. The object {a: 5, b: 6, c:4, d: 3} is more specific than MyType and we convert it to its less specific version, namely a type that only contains the properties a, b and c.

The type of MyType is contained in the type of the object {a: 5, b: 6, c:4, d: 3}.

For TypeScript the poperty d:3 does not exist for Defaults since we have asserted MyType. Thus writing

Defaults.myDefault.d

yields in

Property 'd' does not exist on type 'MyType'.

Of course we could access it at runtime since

type assertions are removed at compile-time, there is no runtime checking associated with a type assertion

On the other hand, when writing

const Defaults = {myDefault: {a: 5, b: 6, d: 9} as MyType}

the type of the object {a: 5, b: 6, d: 9} is neither contained in MyType nor is MyType contained in the type of {a: 5, b: 6, d: 9}.

This correctly yields to the error message

Conversion of type '{ a: number; b: number; d: number; }' to type 'MyType' may be a mistake because neither type sufficiently overlaps with the other.

But, we notice the following. The intersection of MyType and the type of { a: number; b: number; d: number; } is {a: number, b: number}.

Writing

const Defaults = {myDefault: {a: 5, b: 6,d:4} as MyType | {a:number, b:number}}

works. However, the property c can of course not be accessed.

Edit: In response to the comments. The Required construct in TypeScript does what you think it does, but with a minor difference. It turns all optional properties to required, which makes it the opposite of Partial. This has nothing to do with the type we are assigning to a given object, but whether or not the properties of that type are required or optional.

Consider the following.

type MyType = {a: number, b: number, c?: number}

const Defaults = {myDefault: {a: 5, b:2} as MyType}

Defaults.myDefault.c

Checking the access to c yields

(property) c?: number | undefined

However, asserting the type MyType as required

type MyType = {a: number, b: number, c?: number}

const Defaults = {myDefault: {a: 5, b:2} as Required<MyType>}

Defaults.myDefault.c

yields

(property) c: number

Answered By – David Scholz

Answer Checked By – Dawn Plyler (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.