【问题标题】:Type safe merge of object literals in typescript打字稿中对象文字的类型安全合并
【发布时间】:2020-04-28 06:08:33
【问题描述】:

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

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

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

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 ago 用undefined 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 是可选的。不确定如何或是否有可能解决这个问题。

【问题讨论】:

    标签: typescript merge spread-syntax


    【解决方案1】:

    您的两个版本或多或少是等效的 - 只有条件类型中的真/假分支被切换。

    约束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

    【讨论】:

    • 太棒了。谢谢你。是的,更改为K extends keyof T 是关键。我又添加了 3 个测试用例,并提出了建议的更改。随意再次编辑。
    • 实际上,我添加了一些“错误”类型的场景。我目前无法找到解决这些问题的方法,因为它归结为keyof ({[index: string]: number}) => string 匹配所有K in keyof U 并使它们成为never。
    • 嗯,您在这里提出了一些新案例(丢失了编辑答案的轨道)。最初的问题是关于合并两个对象文字。具有显式索引签名类型的对象也引入了另一个维度。我建议,如果答案不能完全满足您的需求,请扩展您的问题,或者改写您自己的答案。干杯!
    • 同意并道歉,大脑 v 累了。我已经编辑了问题标题以使其仅与对象文字有关,我已经进行了小修改(如果您不同意,请再次编辑),并且我在这里提出了一个新问题:stackoverflow.com/q/59689060/539490
    【解决方案2】:

    试试

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

    【讨论】:

    • 请在您的答案中添加一些解释,以便其他人可以从中学习
    【解决方案3】:

    采用@ford04's answer 并将其扩展为多个可选值:

    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)
    

    【讨论】:

      猜你喜欢
      • 2018-09-15
      • 1970-01-01
      • 2019-05-26
      • 2023-03-28
      • 2020-02-27
      • 2019-06-15
      • 1970-01-01
      相关资源
      最近更新 更多