For Typed Functions, with return Interface, is allowed to have non Interface's properties

Viewed 52

Imagine this interface:

interface IUser { 
  id: string;
  name: string;
  age: number;
}

We can have this function:

const fetchUser1 = (userId: string): IUser => { 
  return {
    id: userId,
    name: 'John',
    age: 32,
  };
}

This function cannot return properties that are not in the IUser interface.

So if we try to return for instance a new property the city:

const fetchUser2 = (userId: string): IUser => { 
  return {
    id: userId,
    name: 'John',
    age: 32,
    city: 'Hollabrunn', // TS Error
  };
}

We get the TS error: Object literal may only specify known properties, and 'city' does not exist in type 'IUser' where is absolutely what we want.

Now let's try to make a Type for this function.

type TFetchUser = (userId: string) => IUser;

And implement a function of this Type, also returning by mistake the city property.

const fetchUser3: TFetchUser = (userId) => {
  return {
    id: userId,
    name: 'John',
    age: 32,
    city: 'Hollabrunn',
  };
};

Now here is the problem, we don't have any TS Error. The city property is allowed!

Although if we mistyped the name property to fullName we get the TS Error Property 'name' is missing in type....

Googling, I found some references about this strange behavior of the interfaces, but not a clear resolution.

Could you help?

Playground Link

2 Answers

The spec (3.11.5 Excess Properties) mentions that checks for excess properties are only for direct assignment using fresh object literals.

In your first example, the object literal is directly attempting assignment to an IUser return type, which provokes the checking (and rightly raises an error), but in the second example you have an inserted step that may not be obvious at first.

Because the return type was unspecified, the function is inferred to have a return type of that object literal, not IUser, which means that the return statement is completely valid and no further checks are necessary.

The function is then assigned to a different type which requires a return type of IUser, which is assignment-compatible with your object, but by that time we're no longer talking about assigning an object literal and can safely ignore checks for excess properties.

Thanks to Jared Smith & Chris Hannon, the solution is defining the return type explicitly.

Otherwise, for TS, we implement the IUser and new properties are allowed in this case!

const fetchUser3: TFetchUser = (userId): IUser => {
  return {
    id: userId,
    name: 'John',
    age: 32,
    city: 'Hollabrunn',  // Now we have the TS Error here
  };
};
Related