'TypeScript : avoid extra properties

To make it simple, I have an issue with Typescript's "Excess Property Checks" behavior. I would like to be sure that an object with extra properties is not be accepted by TypeScript.

In my example of a simple interface I could simply pick the available data, but I have a lot of properties and I would like to avoid filtering them on runtime, is there a way?

Here you can find an example code I made for this topic :

TypeScript Playground

type LayoutType {
    margin: number;
}

const badData = {
    margin: 23,
    padding: 23,
}

function func(param: LayoutType) {
    console.log(param);
    // Here I want to use property being sure param only contains LayoutType property
}

// OK
func({ margin: 42 })
// OK : padding is detected as unwanted property
func({ margin: 42, padding: 32 })
// KO : bad data shouldn't fit
func(badData)

/* SAME */

// OK : padding is detected as unwanted property
const test1: LayoutType = { margin: 42, padding: 32 };
// KO : bad data shouldn't fit
const test2: LayoutType = badData;


Solution 1:[1]

Sounds like you want an Exact type. Typescript doesn't come with one, but it's easy to make:

type Exact<A, B> = A extends B
  ? B extends A
    ? A
    : never
  : never

This basically says that if and A extends B and B extends A then the types are identical, neither are a subset or superset of the other. So it should allow that type through. If they are not identical, then the type is never which prevents that type from being allowed.

Now you just need to make your function generic, and enforce that argument to exactly the right type:

function func<T>(param: Exact<T, LayoutType>) {
    console.log(param);
}
func(badData)
// Argument of type '{ margin: number; padding: number; }'
//   is not assignable to parameter of type 'never'.

Playground

More relevant reading here in Typescript issue #12936


Lastly, the reason that an object literal doesn't work, but an object variable does is that the literal is being constructed for a particular type. Typescript can't know know about the extra properties because there is no type information for those properties. So the typescript program can't use those properties, because they aren't declared to exist.

However, when the object is a variable and the extra properties are known, then it is a separate but compatible type. The extra properties may not be used in a function that only accepts the narrow type, but in other code that knows about the wider type the properties can be used.

This is why this is valid:

const obj1 = { a: 1, b: 2 }
const obj2: { a: number } = obj1

console.log(obj1.b) // b can still be accessed

But this is not:

const obj1: { a: number } = { a: 1, b: 2 }
//                                  ^ type error

Solution 2:[2]

The reason

func({ margin: 42, padding: 32 })

doesn't work, but

func(badData)

is because of something called "Excess Property Checking" in TypeScript:

Object literals get special treatment and undergo excess property checking when assigning them to other variables, or passing them as arguments. If an object literal has any properties that the “target type” doesn’t have, you’ll get an error.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Utku