According to some business rules, I created an aggregation function that I need to type correctly.
The function (JS):
It takes:
- Initial object.
n
objects.
export function aggregateObjects(initialObject, ...newObjects) {
const aggregate = (objectA, objectB) => {
return Object.keys(objectB).reduce(
(acc, key) => ({
...acc,
[key]: [...(acc[key] ?? []), ...(objectB[key] ?? [])],
}),
objectA
);
};
return newObjects.reduce(
(aggregatedObjects, newObject) => aggregate(aggregatedObjects, newObject),
initialObject
);
}
How to use it (TypeScript):
Here is an example of two new objects passed to aggregateObjects
, but it should work with 5, 10, ...
const initialObject = {
a: [objectOfType1],
};
const newObject1 = {
a: [objectOfType2],
b: [objectOfType1],
};
const newObject2 = {
c: [objectOfType1, objectOfType2],
};
const result = aggregateObjects(initialObject, [newObject1, newObject2]);
What we expect (TypeScript):
According to previous example, we expect this result:
a
should be typed as an array ofObjectType1
orObjectType2
.b
should be typed as an array ofObjectType1
.c
should be typed as an array ofObjectType1
orObjectType2
.
type Result = {
a: (ObjectType1 | ObjectType2)[];
b: ObjectType1[];
c: (ObjectType1 | ObjectType2)[];
};
What I tried (TypeScript):
Of course, it does not work:
- Internal
aggregate
seems OK (even if there's some issues related to spread). - Outside, I didn't find a solution to correctly type return value.
Note that CombineObjs
is a TS helper inspired from https://dev.to/svehla/typescript-how-to-deep-merge-170c
export function aggregateObjects<T extends object, U extends any[]>(
initialObjects: T,
...newObjects: U
) {
const aggregate = <A extends object, B extends object>(
objectA: A,
objectB: B
): CombineObjs<A, B> => {
return (Object.keys(objectB) as Array<keyof B>).reduce(
(acc, key) => ({
...acc,
[key]: [
// @ts-ignore
...(acc[key as keyof CombineObjs<A, B>] ?? []),
// @ts-ignore
...(objectB[key] ?? []),
],
}),
objectA as unknown as CombineObjs<A, B>
);
};
return newObjects.reduce(
(aggregatedObjects, newObject) => aggregate(aggregatedObjects, newObject),
initialObjects
);
}
Thanks for your solutions.
CodePudding user response:
Here is the typing for the function.
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
function aggregateObjects<
T extends Record<string, any[]
>[]>(objects: [...T]): {
[K in keyof UnionToIntersection<T[number]>]: (T[number] extends infer U ?
U extends Record<K, (infer V)[]>
? V
: never
: never)[]
} {
return {} as any
}
I did not see any reason for the initialObject
to be a separate parameter, since it seems to be handled exactly like the other objects.
const initialObject = {
a: ["objectOfType1"],
};
const newObject1 = {
a: [23],
b: ["objectOfType1"],
};
const newObject2 = {
c: ["objectOfType1", 23],
};
const result = aggregateObjects([initialObject, newObject1, newObject2]);
// const result: {
// a: (string | number)[];
// b: string[];
// c: (string | number)[];
// }