Imagine you have these three objects:
const obj = {
name: 'bob',
};
const obj2 = {
foo: 'bar',
};
const obj3 = {
fizz: 'buzz',
};
A simple merge function has been written to combine these three objects into one:
// Not the best solution as it only takes 3 parameters
const merge = <A extends Record<string, unknown>, B extends Record<string, unknown>, C extends Record<string, unknown>>(...rest: [A, B, C]) => {
return rest.reduce((acc, curr) => ({ ...acc, ...curr }), {}) as A & B & C;
};
// Works fine. Provides expected return type and autocompletion in IDE for properties "foo", "name", and "fizz"
const test1 = merge(obj, obj2, obj3);
However, there are two issues with this approach:
- It currently only supports merging 3 objects. I would like it to support an arbitrary number of objects.
- The generic declaration is overly lengthy.
The function's return type (visible when hovering over test1
) is an intersection of all inferred types from the 3 objects passed in. While this is great, the method could be improved.
An attempt was made to modify the function:
// Trying to accept any number of objects rather than just 3
const merge2 = <T extends Record<string, unknown>[]>(...rest: T) => {
// Combining all objects
return rest.reduce((acc, curr) => ({ ...acc, ...curr }), {});
};
// The function works but the type of test2 ends up being "Record<string, unknown>". Type inference for the objects
// doesn't capture specific properties such as "name", "foo", and "fizz"
const test2 = merge2(obj, obj2, obj3)
Although the function remains the same, efforts were made to allow for multiple objects to be merged. However, the return type always defaults to Record<string, unknown>
, resulting in loss of type information from the passed objects. This means that trying to access test2.f
for autocomplete on foo
won't work.
How can the object types be preserved? And how can an intersection across N number of types be achieved?