Consider following code:
interface IUserData {
FirstName: string,
LastName: string,
Email: string,
Password: string
}
interface IState extends IUserData {
isSuccess: boolean
}
const state: IState = {
FirstName: 'Jan',
LastName: 'Kowalski',
Email: 'email',
Password: 'abc',
isSuccess: true
}
const testFunction = (userData: IUserData): void => {
console.log(userData);
}
testFunction(state)
The code returns:
{
FirstName: 'Jan',
LastName: 'Kowalski',
Email: 'email',
Password: 'abc',
isSuccess: true
}
The question is why does the function even accept the state
object. I'd expect the compiler to at least throw an error that the argument type is incorrect.
Testing this, I wanted to see if there's a possiblity to feed the function with an object whose properties are a subset of original object's properties without mapping them to a new object.
In short, the desired output would be:
{
FirstName: 'Jan',
LastName: 'Kowalski',
Email: 'email',
Password: 'abc',
}
CodePudding user response:
I'd expect the compiler to at least throw an error that the argument type is incorrect.
The argument type is correct. IState
is a subtype of IUserData
, and so anywhere you can use an IUserData
, you can use an IState
. That's standard in object type hierarchies, not just in TypeScript but in Java, C#, and many others. (TypeScript makes one exception to this general rule: when you assign an object specified as a literal to something [let x: ABCType = {a: 1, b: 2, c: 3, d: 4};
where ABCType
only has a
, b
, and c
] or similarly when using an object literal for an argument in a function call, it warns you of "excess properties" — not because it's incorrect from a type perspective, but because it's probably, pragmatically speaking, a coding mistake.)
In short, the desired output would be:
{ FirstName: 'Jan', LastName: 'Kowalski', Email: 'email', Password: 'abc', }
You're logging the parameter value the function received. Nothing in your code does anything to modify or extract properties from that object, so all of the object's properties (including ones from its subtype) are present.
TypeScript doesn't do anything to values, that's a runtime thing, and TypeScript is a compile-time thing (other than the small runtime presence enum
s have). If you want to write a function that removes excess properties, you'll have to do that explicitly with runtime code. TypeScript won't do it for you. (And sadly, doing it where you get the property names from a type definition isn't possible, you have to come at it the other way: deriving a type definition from a model object that actually exists at runtime.)
Here's an example of doing that (I'm not recommending it, I'm just showing you how you can do it if you want to):
const sampleUserData = {
FirstName: "Jan",
LastName: "Kowalski",
Email: "email",
Password: "abc",
};
type IUserData = typeof sampleUserData;
const userDataKeys = new Set(Object.keys(sampleUserData));
const testFunction = (userData: IUserData): void => {
// Filter out non-IUserData properties
const filtered = Object.fromEntries(
Object.entries(userData).filter(([key]) => userDataKeys.has(key))
) as IUserData;
console.log(filtered);
};
Then this:
interface IState extends IUserData {
isSuccess: boolean
}
const state: IState = {
FirstName: "Jan",
LastName: "Kowalski",
Email: "email",
Password: "abc",
isSuccess: true
};
testFunction(state);
logs:
{ "FirstName": "Jan", "LastName": "Kowalski", "Email": "email", "Password": "abc" }