Laurens Laurens - 12 days ago 1121
TypeScript Question

Denormalize Array<A> and Array<B> where clause is true, set B as named property on A

I'm trying to 'denormalize' two arrays (IObservableArray and IObservableArray) where array A has objects with id's that point to id's of objects in array B. I would like to put objects from array B that match the id of the object in array A on object A as a dynamically named property. The arrays are

IObservableArray
s from
mobx
but that should not be of importance.

I'm trying to achieve this:

var aArr: IObservableArray<A extends { bId: number }>;
var bArr: IObservableArray<B extends { id: number }>;

var d = denormalize(aArr, bArr, (a, b) => a.bId === b.id, "b");
console.log(d[0].b) // => B


I tried the following:

export function denormalize<A, B, K extends string, TRet extends A & {[asProperty: K]: B}>(
arr1: IObservableArray<A>,
arr2: IObservableArray<B>,
where: (a: A, b: B) => boolean,
asProperty: K
): IObservableArray<TRet> {
return observable.array(); // stub
}


But it says
[ts] An index signature parameter type must be 'string' or 'number'.
(parameter) k: K extends string


Is there a way to make the
A[asProperty]: B
work so that the compiler knows that the type returned by
denormalize
has an additional property with name
asProperty
and it autocompletes as such?

Answer Source

You can't use a generic type in the index signature, the indexer parameter must be string or number (this is according to the spec).

What you can do is use the mapped type syntax ([Prop in Key] : Type)

export function denormalize<A, B, K extends string, TRet extends A & { [P in K]: B}>(
    arr1: IObservableArray<A>,
    arr2: IObservableArray<B>,
    where: (a: A, b: B) => boolean,
    asProperty: K
): IObservableArray<TRet> {
    // ...
}

Will work :

console.log(d[0].b) // => B 

Edit

As @jcalz mentioned in the comments, the signature can also use the Record<K extends string, T> type which is the same as { [P in K]: B}>. Using this the signature would be :

export function denormalize<A, B, K extends string, TRet extends A &  Record<K,B>>(
    arr1: IObservableArray<A>,
    arr2: IObservableArray<B>,
    where: (a: A, b: B) => boolean,
    asProperty: K
): IObservableArray<TRet>