Type safe merge of object literals in typescript

一个人想着一个人 提交于 2020-01-25 10:14:00

问题


I want to merge two typescript objects (using object spread):

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

I want to use the type system to ensure none of the properties in the second object overwrite any properties in the first. I am not sure why the following solution does not work:

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 which I thought worked at the time had undefined extends U[K] in the place of 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}

Neither of these two work. I suspect it is because the undefined extends U[K] or T[K] extends U[K] are both false as the property K in T is optional. Not sure how or if it's possible to get around this.


回答1:


Both your versions are more or less equivalent - only the true/false branches in the conditional type are switched up.

The constraint T extends {[K in keyof U]?: any} is a bit problematic: when you remove a in two, the error Type '{ a: number; }' has no properties in common with type '{ b?: any; } is triggered, which actually should be the success case.

Be also aware, that the resulting type merge doesn't contain the merged type definition from both types. We can change the declaration up:

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

Now, the compiler correctly errors with a duplicate a property:

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

In the following I have simplified the type a bit and packed everything in a compact helper function to control types + spread operator:

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



来源:https://stackoverflow.com/questions/59684256/type-safe-merge-of-object-literals-in-typescript

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!