typescripttypescript-generics

How can I relax a generic type declaration such that unknown is not inferred


I have a function that groups an array and returns a Map:

function groupBy<ListValueType, KeyValueType extends String|Number>(
    list: ListValueType[],
    keyGetter: (elem: ListValueType) => KeyValueType)
     : Map<KeyValueType, ListValueType[]> 
{
    const map = new Map<KeyValueType, ListValueType[]>();
    list.forEach((item: ListValueType) => {
        const key = keyGetter(item);
        const collection = map.get(key);
        if (!collection) {
            map.set(key, [item]);
        } else {
            collection.push(item);
        }
    });
    return map;
}

ok, this works fine in a standard setting:

const arr = [1,2,3,4,5,6]
const map = groupBy(arr, x => x < 4 ? 'small' : 'large')

but this does not work if the ListValueType is unknown

function someFunction(list) { // list implicitly has 'any' type
   const map = groupBy(list, elem => elem.myProperty) // Error TS18046 elem is inferred as 'unknown'
}

If I removed all type information on my function, typescript would not complain about unknown type.

can I relax my typisation such that the above code is accepted.

I know that any type casts would help, but don't want to change all calls to this function:

function someFunction(list) { // list implicitly has 'any' type
   const map = groupBy(<any[]>list, elem => elem.myProperty) // no type error
}

Solution

  • using a default type is possible `ListValueType = any` and does the job:

    
    function groupBy<KeyValueType extends String|Number, ListValueType = any>(
        list: ListValueType[],
        keyGetter: (elem: ListValueType) => KeyValueType)
         : Map<KeyValueType, ListValueType[]> 
    {
        const map = new Map<KeyValueType, ListValueType[]>();
        list.forEach((item: ListValueType) => {
            const key = keyGetter(item);
            const collection = map.get(key);
            if (!collection) {
                map.set(key, [item]);
            } else {
                collection.push(item);
            }
        });
        return map;
    }