0

I have a strongly-typed collection as follows:

interface IUser {
  id: number,
  name: string
}

const users: IUser[] = [
  { id: 1, name: 'Bob' },
  // ...
];

Then, I create a new collection using the map function:

const nextUsers: IUser[] = users.map((user: IUser) => ({
  ID: 3, // wrong field name
  name: 'Mike',
  id: 3,
}));

As you can see, there is a field with a wrong name - ID. Well, the question is why does it work?))

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
Sergey
  • 5,396
  • 3
  • 26
  • 38

4 Answers4

7

This is a byproduct of how typescript checks for excess properties. A little bit of background:

Type A is generally assignable from type B if type B has at least all the properties of type A. So for example, no error is expected when performing this assignment:

let someObject = { id: 3, name: 'Mike', lastName: 'Bob' }
let user: { id: number, name: string } = someObject // Ok since someobject has all the properties of user
let userWithPass : { id: number, name: string, pass: string } = someObject // Error since someobject does not have pass

The only time Typescript will complain about excess properties is when we try to directly assign an object literal to some object that has a known type:

// Error excess property lastName
let user: { id: number, name: string } =  { id: 3, name: 'Mike', lastName: 'Bob' }

Now in your case, typescript will first infer the type of the result to map to the type of the returned object literal, which is all valid, and then will check that this type is compatible with the IUser array which it is, so no error since we never directly tried to assign the object literal to something of type IUser.

We can ensure we get an error if we explicitly set the return type of the arrow function passed to map

const nextUsers: IUser[] = users.map((user: IUser) : IUser => ({
    id: 3,
    name: 'Mike',
    ID: 152,
}));
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
1

An interface defines some properties an object must have, but it is not exhaustive; the object can have other properties as well. This is demonstrated in the docs here.

Simon Brahan
  • 2,016
  • 1
  • 14
  • 22
1

It works because the properties of the interface are defined. The interface does not prevent additional properties from being added.

However :

const nextUsers: IUser[] = users.map((user: IUser) => ({
  ID: 3, // wrong field name
  name: 'Mike',
}));

This results in an error from the TypeScript compiler.

hlobit
  • 166
  • 6
0

It stills works but if you had a typescript linter you would have an ERROR because the returned array type is different then IUse[]

Melchia
  • 22,578
  • 22
  • 103
  • 117