TypeScript: Excess Property Checks in Object Literals

Updated: January 7, 2024 By: Guest Contributor Post a comment

Overview

TypeScript’s excess property checks help ensure objects match expected shapes, avoiding potential bugs from stray properties.

Introduction

TypeScript, as a statically typed superset of JavaScript, provides increased safety through type checking. One such feature is the ‘excess property check’ for object literals. This check prevents the common error of providing properties that an object type does not expect. In this tutorial, we’ll explore how TypeScript achieves this through a variety of examples, ranging from basic to more nuanced advanced scenarios, and how to work with or around these checks when needed.

Basic Example of Excess Property Checks

Let’s start with a straightforward example:

interface Person {
    name: string;
    age: number;
}

let john: Person = {
    name: 'John Doe',
    age: 30,
    occupation: 'Developer' // TypeScript error: Object literal may only specify known properties...
};

In the code snippet above, TypeScript will throw an error because the ‘Person’ interface does not have an ‘occupation’ property, and yet we have tried to assign it in our ‘john’ object declaration.

Why Enforce Excess Property Checks?

Excess property checking is critical for several reasons; it keeps the object structure predictable and type-safe, prevents potential bugs that can arise from typos or misunderstandings of the object structure, and enforces a cleaner and more intentional codebase.

Working with Excess Property Checks

There are times when you might need to supply extra properties in an object that don’t exist on the expected type. In such cases, TypeScript provides a few workarounds:

interface Person {
    name: string;
    age: number;
}

// Using type assertion
let jane: Person = {
    name: 'Jane Doe',
    age: 28,
    occupation: 'Designer'
} as Person;

// Adding an index signature to the interface
interface FlexiblePerson {
    name: string;
    age: number;
    [key: string]: any;
}

let sam: FlexiblePerson = {
    name: 'Sam Smith',
    age: 35,
    occupation: 'Artist'
};

In the first work-around, we use type assertion to bypass the additional property check. In the second, we modify the interface to include an index signature, allowing any number of string keys with any value type.

Advanced Scenarios

In more complex cases, such as when dealing with nested objects or leveraging intersection types, understanding and handling excess property checks become even more important. Let’s look at a complex example:

interface BasePerson {
    name: string;
    age: number;
}

interface Employee extends BasePerson {
    department: string;
}

let developer: Employee = {
    name: 'Max Power',
    age: 38,
    department: 'Engineering',
    skills: ['TypeScript', 'JavaScript']
    // Error: 'skills' does not exist in type 'Employee'
};

To solve this, we need another technique such as merging multiple interfaces or altering the assignment pattern:

interface Employee {
    department: string;
}

interface SkilledPerson {
    skills: string[];
}

type SkilledEmployee = Employee & SkilledPerson;

let developer: SkilledEmployee = {
   // No error, 'skills' is allowed by 'SkilledEmployee' type
   ...proper object shape
};

This shows how combining interfaces into a new type using intersection types allows the object to pass the excess property checks by expecting properties from both interfaces.

Conclusion

In closing, TypeScript’s excess property checks are a powerful feature that enhances code reliability by enforcing object shapes. This tutorial covered the basics as well as some advanced scenarios, each with examples to ensure clarity. Properly leveraging these checks leads to more maintainable and bug-free code. Embrace the rigor TypeScript offers, and happy coding!