Search code examples
typescripttypescript-generics

Aggregating object types that should match


I'm trying to enforce two generic function parameters matching each other so they can be combined together. The enforcement seems to work but I can't seem to combine the values without an issue from the compiler.

function keys<T extends object>(obj: T) {
  return Object.keys(obj) as (keyof T)[];
}

function combineObjs<A extends object, B extends Record<keyof A, number>>(valsA: A, valsB: B) {

    // const aggr: Partial<T> = {}
    const aggr = { ...valsB }

    keys(valsA).forEach( key => {
        // Operator '+' cannot be applied to types 'A[keyof A]' and 'B[keyof A]'.
        aggr[key] = valsA[key] + valsB[key]
    })
    return aggr // as Required<T>
}

const result = combineObjs({ foo: 1 }, { foo: 2 })

Solution

  • Since you define A as object, it can contain any property value types like objects, which aren't suitable for + operator.

    And seems we need an approach of accepting any A object but limiting B to numeric properties of A. It uses casting but "matches" the runtime well:

    Playground

    type PickValues<T extends object, V> = {[K in keyof T]: T[K] extends V ? T[K]: never};
    
    function combineObjs<A extends object, B extends Partial<PickValues<A, number>>>(valsA: A, valsB: B) {
    
        const aggr = {} as Record<keyof A, number>;
    
        for(const key in valsA){
            if(typeof valsA[key] === 'number'){
                aggr[key] = valsA[key] + (valsB[key] ?? 0);
            }
        }
    
        return aggr as PickValues<A, number>;
    }
    
    const result = combineObjs({ foo: 1, boo: 2}, { foo: 2});
    console.log(result);