问题
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