I'm trying to learn Typescript and I've been following some exercises I've found on the internet. But the feedback on incorrect solutions aren't that great. So I've got the following code but TS keeps complaining that property type
is not defined on T:
interface User {
type: 'user';
name: string;
age: number;
occupation: string;
}
interface Admin {
type: 'admin';
name: string;
age: number;
role: string;
}
export type Person = User | Admin;
export const persons: Person[] = [
{ type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' },
{ type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' },
{ type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' },
{ type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' },
{ type: 'user', name: 'Wilson', age: 23, occupation: 'Ball' },
{ type: 'admin', name: 'Agent Smith', age: 23, role: 'Anti-virus engineer' }
];
export function filterPersons<T>(persons: T[], personType: string, criteria: T): T[] {
return persons
.filter((person) => person.type === personType)
.filter((person) => {
let criteriaKeys = Object.keys(criteria) as (keyof T)[];
return criteriaKeys.every((fieldName) => {
return person[fieldName] === criteria[fieldName];
});
});
}
export const usersOfAge23 = filterPersons<User>(persons, 'user', { age: 23 });
export const adminsOfAge23 = filterPersons<Admin>(persons, 'admin', { age: 23 });
What am I doing wrong here? T should be a genertic type or in this case User or Admin that is passed right?
CodePudding user response:
In your function declaration, there is one thing you know about T
: it extends Person
so give this hint to TypeScript:
export function filterPersons<T extends Person>(persons: T[], personType: string, criteria: T): T[] {
Your function implementation will work like this but it raises two other errors:
- You are passing a
Person[]
tofilterPersons<User>
andfilterPersons<Admin>
which is wider than the required types. Quick fix: let TypeScript infer the type by omitting it when you call the function:filterPersons
- The criteria is not a full
T
, only some properties of it so type it asPartial<T>
CodePudding user response:
Inferring types in a filter
only works if you don't use an arrow (source). So you'd need something like:
export function filterPersons<T extends Person>(
persons: Person[],
personType: T['type'],
criteria: Partial<T>
): T[] {
const isPersonOf = (person: Person): person is T => person.type === personType;
return persons
.filter(isPersonOf)
.filter((person) => {
let criteriaKeys = Object.keys(criteria) as (keyof T)[];
return criteriaKeys.every((fieldName) => {
return person[fieldName] === criteria[fieldName];
});
});
}
Note the type of usersOfAge23
(User[]
) and adminsOfAge23
(Admin[]
) when using this implementation.