Search code examples
typescriptgeneric-function

Generic function parameter restricted to a property of T


I have a uniqBy function defined thus:

export function uniqBy<T>(a: T[], key: any): T[] {
    const seen = {};
    return a.filter(function (item) {
        if (item) {
            const k = key(item);
            return seen.hasOwnProperty(k) ? false : (seen[k] = true);
        } else {
            return false;
        }
    });
}

This strongly types my return value based on the input array, but I would also like key parameter to be strongly typed so that I get compile-time errors if I try to pass in a property that doesn't exist on T. example of current usage:

uniqArray = uniqBy(this._checkups, x => x.CatDescription);

_checkups being an array of the following:

export interface Checkup {
  id: string;
  CatDescription: string;
  ProcessTitle: string;
  MsgProcessID: number;
  MsgDate: string;
  MsgStatus: number;
  MsgText: string;
  MsgAction: string;
  MsgEntityID: string;
  MsgEntity: string;
}

I would like it if trying to do the following:

uniqArray = uniqBy(this._checkups, x => x.NonExistantProperty);

gave me a compile-time error (and IntelliSense on the property completion). How would I define key in the parameters in order to do this?

This would return an array of items with only unique values of CatDescription (taking the first object with a CatDescription in the event of duplicates).

I don't know what this is called, it's not a Predicate<T> as that is for filtering and returns a boolean.


Solution

  • Seems like as second parameter you pass callback but you don't have a type for it so you an just give it a type (val: T) => T[keyof T]

    export function uniqBy<T>(a: T[], key: (val: T) => T[keyof T]): T[] {
        const seen = {};
        return a.filter(function (item) {
            if (item) {
                const k = key(item);
                return seen.hasOwnProperty(k) ? false : (seen[k] = true);
            } else {
                return false;
            }
        });
    }
    

    Then by adding non existent key it will give you an error

    uniqArray = uniqBy<Checkup>(this._checkups, x => x.NonExistantProperty);