The `Override` Type

Renato Ribeiro
Renato Ribeiro

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

Follow me on twitter, and github.