2

Given an arbitrary number of objects of different shape, is it possible for TypeScript to properly assert type information?

Example:

const merge = (...objects) => {
  return Object.values(objects).reduce((acc, next) => ({ ...acc, ...next }));
}

My actual use case is a lot more involved than this, but the type signature would be similar.

Here's what works, but I hope is not the only solution:

function merge<T1, T2>(...objects: [T1, T2]): T1 & T2;
function merge<T1, T2, T3>(...objects: [T1, T2, T3]): T1 & T2 & T3;
function merge(...objects) {
  return Object.values(objects).reduce((acc, next) => ({ ...acc, ...next }));
}

My issues with this are that it fails to compile with the newer versions of TypeScript and it's not at all maintainable.

Is there a better way to achieve this?

2 Answers2

0

After a couple more rounds of experimenting, here's a solution, that albeit works - I'd still like to see some improvement upon:

function merge<R>(...objects: Partial<R>[]): R {
  return Object.values(objects).reduce((acc, next) => ({ ...acc, ...next }));
}

It approaches the issue from the other side - defining the return type and the signature qualifies any partial implementations of what's returned.

0

Here is a simpler way to implement the function is using Object.assign(), and a more complicated way to type the function that works a bit better since the generic type can be inferred:

Playground Link

// https://stackoverflow.com/a/50375286/2398020
type UnionToIntersection<U> =
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;

function merge<T extends {}[]>(...objects: T): UnionToIntersection<T[number]> {
    return Object.assign({}, ...objects);
}

const x = merge({ foo: 5 }, { bar: 'world' }, { qux: [ 10, 'asdf' ] });
// const x: {
//     foo: number;
// } & {
//     bar: string;
// } & {
//     qux: (string | number)[];
// }

Note however, if there are duplicate keys it may not type them correctly (it should have the later objects override the former)

const y = merge({ foo: 5 }, { foo: 'world' });
// const y: {
//     foo: number;
// } & {
//     foo: string;
// }
// But should be { foo: string }
Mingwei Samuel
  • 2,917
  • 1
  • 30
  • 40