'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 :
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'.
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 |