It would be reasonable to assume that the compiler will generate an error indicating that the argument type is incorrect.
In fact, the argument type is accurate. The IState
interface is a subtype of IUserData
, meaning whenever you can use an IUserData
, you can also use an IState
. This behavior is standard in object type hierarchies, not only in TypeScript but also in languages like Java, C#, and others. (TypeScript does make one exception to this rule: when assigning a literal object to something [
let x: ABCType = {a: 1, b: 2, c: 3, d: 4};
where
ABCType
has only
a
,
b
, and
c
], or using an object literal as an argument in a function call, it warns about "excess properties" — not because it's wrong from a type perspective, but likely due to coding mistakes.)
Ultimately, the expected output should look like:
{
FirstName: 'Jan',
LastName: 'Kowalski',
Email: 'email',
Password: 'abc',
}
The logged value in your code is simply the parameter received by the function. No modification or extraction of properties occurs, so all properties of the object (including those from its subtype) are present.
TypeScript does not manipulate values; that's a runtime task, whereas TypeScript operates at compile time (except for minor runtime aspects like enum
s). If you wish to create a function that eliminates excess properties, you must handle that explicitly with runtime code; TypeScript won't handle it automatically. Unfortunately, generating property names from a type definition isn't feasible; instead, you need to derive a type definition from an existing model object during runtime.
Here's an example demonstrating this approach (not necessarily recommended, but illustrates how it can be done):
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 => {
// Filtering out non-IUserData properties
const filtered = Object.fromEntries(
Object.entries(userData).filter(([key]) => userDataKeys.has(key))
) as IUserData;
console.log(filtered);
};
Subsequently:
interface IState extends IUserData {
isSuccess: boolean
}
const state: IState = {
FirstName: "Jan",
LastName: "Kowalski",
Email: "email",
Password: "abc",
isSuccess: true
};
testFunction(state);
outputs:
{
"FirstName": "Jan",
"LastName": "Kowalski",
"Email": "email",
"Password": "abc"
}
Playground link