Home > front end >  Typescript: Property doesn't exist on Generic Type
Typescript: Property doesn't exist on Generic Type

Time:02-10

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[] to filterPersons<User> and filterPersons<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 as Partial<T>

TypeScript playground

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.

  • Related