在打字稿中键入对象文字的安全合并

Type safe merge of object literals in typescript

我想合并两个打字稿对象(使用对象展开):

var one = { a: 1 }
var two = { a: 2, b: 3 }
var m = {...one, ...two} // problem as property `a` is overwritten

我想使用类型系统来确保 none 第二个对象中的属性覆盖第一个对象中的任何属性。我不确定为什么以下解决方案不起作用:

type UniqueObject<T extends {[K in keyof U]?: any}, U> =
    {[K in keyof U]: T[K] extends U[K] ? never : U[K]}

var one = { a: 1 }
var two1 = { a: 2, b: 3 }
var two1_: UniqueObject<typeof one, typeof two1> = two1 // errors correctly
var two2 = { a: undefined, b: 1 }
var two2_: UniqueObject<typeof one, typeof two2> = two2 // passes incorrectly
我认为当时有效的

Another version from a year agoundefined extends U[K] 代替了 T[K] extends U[K]:

type UniqueObject<T extends {[K in keyof U]?: any}, U> =
    {[K in keyof U]: undefined extends T[K] ? U[K]: never}

这两个都不行。我怀疑这是因为 undefined extends U[K]T[K] extends U[K] 都是假的,因为 T 中的 属性 K 是可选的。不确定如何或是否有可能解决这个问题。

您的两个版本或多或少是相同的 - 只有条件类型中的 true/false 分支被切换。

约束T extends {[K in keyof U]?: any}有点问题:当你在two中删除a时,会触发错误Type '{ a: number; }' has no properties in common with type '{ b?: any; },这实际上应该是成功的案例。

另请注意,结果类型 merge 不包含两种类型的合并类型定义。我们可以向上更改声明:

type UniqueObject<T, U> =
    T & { [K in keyof U]: K extends keyof T ? never : U[K] }

现在,编译器正确地出错了 a 属性:

var one = { a: 1 }
var two = { a: 2, b: 3 }
//                                                        v a becomes never here
type Merge = UniqueObject<typeof one, typeof two> // { a: never; b: number; }
const res: Merge = { ...one, ...two } // errors now, cannot assign number to never

在下文中,我稍微简化了类型并将所有内容打包到一个紧凑的辅助函数中以控制类型 + 扩展运算符:

function mergeUnique<T extends object, U extends object & { [K in keyof U]: K extends keyof T ? never : U[K] }>(o1: T, o2: U) {
    return { ...o1, ...o2 }
}

const res21 = mergeUnique({ a: 1 }, { b: 3 })
const res22 = mergeUnique({ a: 1 }, { a: 2, b: 3 }) // error
const res23 = mergeUnique({ a: 1, c: 5 }, { b: 3 })
const res24 = mergeUnique({ a: 1}, { a: undefined }) // error

Code sample

尝试

type Merge<A, B> = { [K in keyof (A | B)]: K extends keyof B ? B[K] : A[K] };

采用 并将其扩展为多个可选值:

function safe_merge<
    O1,
    O2 extends { [K2 in keyof O2]: K2 extends keyof O1 ? never : O2[K2] },
    O3 extends { [K3 in keyof O3]: K3 extends keyof O1 ? never : (K3 extends keyof O2 ? never : O3[K3]) },
    O4 extends { [K4 in keyof O4]: K4 extends keyof O1 ? never : (K4 extends keyof O2 ? never : (K4 extends keyof O3 ? never : O4[K4])) },
    O5 extends { [K5 in keyof O5]: K5 extends keyof O1 ? never : (K5 extends keyof O2 ? never : (K5 extends keyof O3 ? never : ( K5 extends keyof O4 ? never : O5[K5]))) },
    O6 extends { [K6 in keyof O6]: K6 extends keyof O1 ? never : (K6 extends keyof O2 ? never : (K6 extends keyof O3 ? never : ( K6 extends keyof O4 ? never : (K6 extends keyof O5 ? never : O6[K6])))) },
    O7 extends { [K7 in keyof O7]: K7 extends keyof O1 ? never : (K7 extends keyof O2 ? never : (K7 extends keyof O3 ? never : ( K7 extends keyof O4 ? never : (K7 extends keyof O5 ? never : (K7 extends keyof O6 ? never : O7[K7]))))) },
    O8 extends { [K8 in keyof O8]: K8 extends keyof O1 ? never : (K8 extends keyof O2 ? never : (K8 extends keyof O3 ? never : ( K8 extends keyof O4 ? never : (K8 extends keyof O5 ? never : (K8 extends keyof O6 ? never : (K8 extends keyof O7 ? never : O8[K8])))))) },
    O9 extends { [K9 in keyof O9]: K9 extends keyof O1 ? never : (K9 extends keyof O2 ? never : (K9 extends keyof O3 ? never : ( K9 extends keyof O4 ? never : (K9 extends keyof O5 ? never : (K9 extends keyof O6 ? never : (K9 extends keyof O7 ? never : (K9 extends keyof O8 ? never : O9[K9]))))))) },
>(
    o1: O1,
    o2: O2 = ({} as any),
    o3: O3 = ({} as any),
    o4: O4 = ({} as any),
    o5: O5 = ({} as any),
    o6: O6 = ({} as any),
    o7: O7 = ({} as any),
    o8: O8 = ({} as any),
    o9: O9 = ({} as any),
): O1 & O2 & O3 & O4 & O5 & O6 & O7 & O8 & O9 {
    return { ...o1, ...o2, ...o3, ...o4, ...o5, ...o6, ...o7, ...o8, ...o9 }
}


const obj_1 = {1:1}
const obj_2 = {2:1}
const obj_3 = {3:1}
const obj_4 = {4:1}
const obj_5 = {5:1}
const obj_6 = {6:1}
const obj_7 = {7:1}
const obj_8 = {8:1}
const obj_9 = {9:1}


// should not error
safe_merge(obj_1)
safe_merge(obj_1, obj_2)
safe_merge(obj_1, obj_2, obj_3)
safe_merge(obj_1, obj_2, obj_3, obj_4)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7, obj_8)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7, obj_8, obj_9)


// declare objects with keys conflicting with existing objects
const obj_2_1 = {2:1, 1: 1}
const obj_2_1b = {2:1, 1: undefined}

const obj_3_1 = {3:1, 1:1}
const obj_3_2 = {3:1, 2:1}

const obj_4_1 = {4:1, 1:1}
const obj_4_2 = {4:1, 2:1}
const obj_4_3 = {4:1, 3:1}

const obj_5_1 = {5:1, 1:1}
const obj_5_2 = {5:1, 2:1}
const obj_5_3 = {5:1, 3:1}
const obj_5_4 = {5:1, 4:1}

const obj_6_1 = {6:1, 1:1}
const obj_6_2 = {6:1, 2:1}
const obj_6_3 = {6:1, 3:1}
const obj_6_4 = {6:1, 4:1}
const obj_6_5 = {6:1, 5:1}

const obj_7_1 = {7:1, 1:1}
const obj_7_2 = {7:1, 2:1}
const obj_7_3 = {7:1, 3:1}
const obj_7_4 = {7:1, 4:1}
const obj_7_5 = {7:1, 5:1}
const obj_7_6 = {7:1, 6:1}

const obj_8_1 = {8:1, 1:1}
const obj_8_2 = {8:1, 2:1}
const obj_8_3 = {8:1, 3:1}
const obj_8_4 = {8:1, 4:1}
const obj_8_5 = {8:1, 5:1}
const obj_8_6 = {8:1, 6:1}
const obj_8_7 = {8:1, 7:1}

const obj_9_1 = {9:1, 1:1}
const obj_9_2 = {9:1, 2:1}
const obj_9_3 = {9:1, 3:1}
const obj_9_4 = {9:1, 4:1}
const obj_9_5 = {9:1, 5:1}
const obj_9_6 = {9:1, 6:1}
const obj_9_7 = {9:1, 7:1}
const obj_9_8 = {9:1, 8:1}


// should error
safe_merge(obj_1, obj_2_1)
safe_merge(obj_1, obj_2_1b)

safe_merge(obj_1, obj_2, obj_3_1)
safe_merge(obj_1, obj_2, obj_3_2)

safe_merge(obj_1, obj_2, obj_3, obj_4_1)
safe_merge(obj_1, obj_2, obj_3, obj_4_2)
safe_merge(obj_1, obj_2, obj_3, obj_4_3)

safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5_1)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5_2)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5_3)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5_4)

safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6_1)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6_2)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6_3)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6_4)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6_5)

safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7_1)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7_2)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7_3)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7_4)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7_5)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7_6)

safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7, obj_8_1)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7, obj_8_2)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7, obj_8_3)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7, obj_8_4)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7, obj_8_5)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7, obj_8_6)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7, obj_8_7)

safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7, obj_8, obj_9_1)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7, obj_8, obj_9_2)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7, obj_8, obj_9_3)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7, obj_8, obj_9_4)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7, obj_8, obj_9_5)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7, obj_8, obj_9_6)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7, obj_8, obj_9_7)
safe_merge(obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7, obj_8, obj_9_8)