TypeScript: Error when 'extra' properties appear in object literals

What’s the Change?

Old Behavior

Prior to 1.6, TypeScript didn’t do a good job detecting problems in object literals, especially when the property you tried to specify was optional:

interface TextOptions {
    alignment?: string;
    color?: string;
    padding?: number;
}
function drawText(opts: TextOptions) { ... }

// None of these were errors before
drawText({ align: 'center' }); // Oops
drawText({ colour: 'grey' }); // Oops
drawText({ pading: 32}); // Oops

New Behavior

As of 1.6, properties in object literals that do not have a corresponding property in the type they’re being assigned to are flagged as errors:

drawText({ align: 'center' }); // Error, no property 'align' in 'TextOptions'

But I meant to do that

There are a few cases where you may have intended to have extra properties in your object. Depending on what you’re doing, there are several appropriate fixes

Type-checking only some properties

Sometimes you want to make sure a few things are present and of the correct type, but intend to have extra properties for whatever reason. Type assertions (<T>v or v as T) do not check for extra properties, so you can use them in place of a type annotation:

interface Options {
    x?: string;
    y?: number;
}

// Error, no property 'z' in 'Options'
let q1: Options = { x: 'foo', y: 32, z: 100 };
// OK
let q2 = <Options>{ x: 'foo', y: 32, z: 100 };
// Still an error (good):
let q3 = <Options>{ x: 100, y: 32, z: 100 };

These properties and maybe more

Some APIs take an object and dynamically iterate over its keys, but have ‘special’ keys that need to be of a certain type. Adding a string indexer to the type will disable extra property checking

Before

interface Model {
  name: string;
}
function createModel(x: Model) { ... }

// Error
createModel({name: 'hello', length: 100});

After

interface Model {
  name: string;
  [others: string]: any;
}
function createModel(x: Model) { ... }

// OK
createModel({name: 'hello', length: 100});

This is a dog or a cat or a horse, not sure yet

interface Animal { move; }
interface Dog extends Animal { woof; }
interface Cat extends Animal { meow; }
interface Horse extends Animal { neigh; }

let x: Animal;
if(...) {
  x = { move: 'doggy paddle', woof: 'bark' };
} else if(...) {
  x = { move: 'catwalk', meow: 'mrar' };
} else {
  x = { move: 'gallop', neigh: 'wilbur' };
}

Two good solutions come to mind here

Specify a closed set for x

// Removes all errors
let x: Dog|Cat|Horse;

or Type assert each thing

// For each initialization
  x = <Dog>{ move: 'doggy paddle', woof: 'bark' };

This type is sometimes open and sometimes not

A clean solution to the “data model” problem using intersection types:

interface DataModelOptions {
  name?: string;
  id?: number;
}
interface UserProperties {
  [key: string]: any;
}
function createDataModel(model: DataModelOptions & UserProperties) {
 /* ... */
}
// findDataModel can only look up by name or id
function findDataModel(model: DataModelOptions) {
 /* ... */
}
// OK
createDataModel({name: 'my model', favoriteAnimal: 'cat' });
// Error, 'ID' is not correct (should be 'id')
findDataModel({ ID: 32 });

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Reactions: 8
  • Comments: 34 (24 by maintainers)

Commits related to this issue

Most upvoted comments

@JsonFreeman,

Regarding the actual implementation by @ahejlsberg, if I’m correct the following won’t be an error:

interface Foo {
  daiquiri?: string;
}

function getFoo(params: Foo) { }

getFoo({ daquiri: 'a bit rum' }); // No error

I guess the solution to get the additional checks is the following:

type Foo  = {
  daiquiri?: string;
}

function getFoo(params: Foo) { }

getFoo({ daquiri: 'a bit rum' }); // Error, excess property `daquiri`

Have I understood the implementation correctly? Or will types defined via interfaces be also checked?

@RyanCavanaugh @JsonFreeman I’ve put up my code in #3823.