Reputation: 4152
I have an API controller that takes a body parameter like so
insertUser(@Body() user: IUser) {}
However the issue is that I can post an object containing more properties than what IUser
defines.
What is the simplest cleanest way to either ensure that user
strictly respects the type, or clone it keeping only whats defined in the interface IUser` ?
Upvotes: 0
Views: 439
Reputation: 20132
My answer will be framework agnostic. You ask about how to force TS to not allow to pass values of types which have additional fields, it means we want to have exclusive behavior of the type, you have the same properties or you are not allowed.
First of all such behavior isn't beneficial at the type level, as if the given value has fields we require to have, then other fields should not matter for us. Another thing is even though we can do compile time checker of types, if your data comes from API then you need to make validation at the runtime level.
First of all type level exclusive type behavior implementation:
type IUser = {
name: string;
lastname: string;
}
// utility type to enforce exlusive type
type OnlyExistingFields<Champion, Pretendent> = {
[K in keyof Pretendent]: K extends keyof Champion ? Pretendent[K] : never
}
// example type with more fields
type IMoreUser = IUser & {
age: number;
}
// instance of correct type
const u1: IUser = {
lastname: '',
name: ''
}
// instance of type with more fields
const u2: IMoreUser = {
lastname: '',
name: '',
age: 12
}
// below type declaration of exlusive function
declare function insertUser<U extends OnlyExistingFields<IUser, U>>(user: U): void
// usage:
insertUser(u1) // 👍 correct type is the same
insertUser(u2) // 🔴 error type has more fields
Above is fully type level check that we can pass only argument of the specific type, any superset of this type will be not allowed.
As I said above solution is only type level, in reality there is no way to enforce at type level if the object has or not wanted interface at the runtime, everything is only an assumption.
That said we need to have also runtime converter in order to remove all not wanted fields. Consider such converter:
// converts object with probably more fields to object with only wanted fields
function toIUser<T extends IUser>(a: T): IUser {
return {
name: a.name,
lastname: a.lastname
}
}
const u3 = toIUser(u2); // u3 is IUser and also in runtime has only IUser fields
Upvotes: 1