The `Override` Type
When intersecting two objects, typescript rightfully intersects its properties too. It's the expected behavior. But sometimes you may want to merge objects where the right member properties takes precedence. Like the behavior of { ...left, ...right }
.
type Override<Left, Right> = Omit<Left, keyof Right> & Right;
Given Left and Right example types:
type Left = { foo: string; bar: string };type Right = { foo: number };
Intersecting
type Intersected = Left & Right;// type Intersected = { foo: never; bar: string }// └╮ `foo` becomes `string & number`// │ which evaluates to `never` since it can't// │ be string and number at the same time
Overriding
type Overrided = Override<Left, Right>;// type Overrided = { foo: number; bar: string }// └╮ `foo` becomes `number` because// │ Right properties take precedence over Left,// │ but preserving Left properties as expected
# Flatten
To a better readability you might want to combo it with Flatten
type utility.
This tricky type is also known as Simplify
in some libs.
type Flatten<T> = { [Key in keyof T]: T[Key] } & {};type Override1<L extends object, R extends object> = Omit<L, keyof R> & R;type Override2<L extends object, R extends object> = Flatten<Omit<L, keyof R> & R>;
type Left = { foo: string };type Right = { foo: number };
type A = Override1<Left, Right>;// type A = Omit<Left, "foo"> & Right
type B = Override2<Left, Right>;// type B = { foo: number }// └ the type becomes "flat" i.e. easier to read with intellisense
# Assign !==
Spread
Taking advantage of the subject, I want to address a related common confusion.
In TypeScript, object assignment and object spreading leads to different types.
const left = { foo: "string" };const right = { foo: 1 };
const assign1 = Object.assign(left, right);assign1.foo// ^? (property) foo: never
const assign2 = Object.assign({}, left, right);assign2.foo// ^? (property) foo: never
const spread = { ...left, ...right };spread.foo// ^? (property) foo: number
However it seems to be a TypeScript-only problem(?). In runtime it overwrite from right to left aswell.
const assign = Object.assign(left, right);console.log(assign.foo) // logs 1// ^? (property) foo: never