将元组对象合并为一种对象类型

时间:2019-03-07 14:41:39

标签: typescript object spread-syntax

此功能必须支持任意数量的参数:

type Result<T extends object[]> = "???"

function merge<T extends object[]>(...sources: T): Result<T> {
  return Object.assign({}, ...sources)
}

预期结果类型为(playground)

的示例输入
type Expected = {
  a: 2
  b: 1 | 2
  c?: 1
  d?: 1 | 2
  e: 2
  f: 2
}

// The return type should match `Expected` exactly. No intersections please!
const result: Expected = merge(
  {} as {
    a: 1
    b: 1
    c?: 1
    d?: 1
    e?: 1
  },
  {} as {
    a: 2
    b?: 2
    d?: 2
    e: 2
    f: 2
  }
)

相关问题:Typescript, merge object types?

2 个答案:

答案 0 :(得分:1)

简短的答案是,您不能对任意数量的值执行此操作。稍长一点的答案是您不应该尝试这样做,因为自然的递归定义{{​​3}}和可以诱骗编译器执行此操作的各种方法是will not work

如果您愿意支持最多一些合理但有限的参数,则可以。实际上,officially frowned upon当前只是路口的一些重载。尽管这个standard library definition of Object.assign(),对于人们来说似乎运作良好。

假设我们以might change sometime中的Spread<L, R>为起点,那么我们可以制作自己的SpreadTuple,使其适用于固定长度的任何内容:

type Tail<L extends any[]> =
  ((...l: L) => void) extends ((h: infer H, ...t: infer T) => void) ? T : never;

type SpreadTuple<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTuple1<Tail<T>>>
type SpreadTuple1<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTuple2<Tail<T>>>
type SpreadTuple2<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTuple3<Tail<T>>>
type SpreadTuple3<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTuple4<Tail<T>>>
type SpreadTuple4<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTuple5<Tail<T>>>
type SpreadTuple5<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTuple6<Tail<T>>>
type SpreadTuple6<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTuple7<Tail<T>>>
type SpreadTuple7<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTuple8<Tail<T>>>
type SpreadTuple8<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTuple9<Tail<T>>>
type SpreadTuple9<T extends {}[], L extends number = T['length']> = L extends 0 ? never : L extends 1 ? T[0] : Spread<T[0], SpreadTupleX<Tail<T>>>
type SpreadTupleX<T extends {}[]> = T[number]; // give up

我这样做是为了让您看到如何轻松地将其扩展到您关心的任何长度。如果您愿意对许多Tail进行硬编码,则可以不使用Spread<Spread<Spread<....>>>来做到这一点。

无论如何,现在这有效:

// use default parameter R to expand result to easy-to-digest type
function merge<T extends object[], R = SpreadTuple<T>>(...sources: T): { [K in keyof R]: R[K] } {
  return Object.assign({}, ...sources);
}

const result: Expected = merge(
  {} as {
    a: 1
    b: 1
    c?: 1
    d?: 1
    e?: 1
  },
  {} as {
    a: 2
    b?: 2
    d?: 2
    e: 2
    f: 2
  }
)
//const result: {
//  c?: 1 | undefined;
//  a: 2;
//  e: 2;
//  f: 2;
//  b: 1 | 2;
//  d: 1 | 2 | undefined;
//}

让我们尝试使用两个以上自变量的方法:

const r = merge({ a: 1, b: 2 }, { b: "3", c: "4" }, { c: true, d: false });
// {  a: number;  b: string;  c: boolean;  d: boolean; }

对我很好。

希望有帮助。祝你好运!

答案 1 :(得分:0)

type Result<T extends object[]> = UnionToIntersection<T[number]>

/**
 * @author https://stackoverflow.com/users/2887218/jcalz
 * @see https://stackoverflow.com/a/50375286/10325032
 */
type UnionToIntersection<Union> =
  (Union extends any
    ? (argument: Union) => void
    : never
  ) extends (argument: infer Intersection) => void
      ? Intersection
      : never;

请参见{{3}}。