Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to merge 2 partials to a complete object in Typescript?

Tags:

typescript

See a playground of this question here

I have an interface that requires all props. However, creating the object is a two-step process. Therefore I am planning to create 2 Partial versions of the object, and then merge them together to satisfy the non-partial interface. Example:

interface IComplete {
    a: string,
    b: string
}

const part1: Partial<IComplete> = {
    a: 'hello'
}

const part2: Partial<IComplete> = {
    b: 'world'
}

const together: IComplete = {
    ...part1,
    ...part2
}

Even though together is always complete, the compiler complains:

Type '{ a?: string; b?: string; }' is not assignable to type 'IComplete'. Property 'a' is optional in type '{ a?: string; b?: string; }' but required in type 'IComplete'.

Is there a recommended way to achieve this? It is important for my case that the interface IComplete is not made partial. I.e. the props a and b are never null or undefined.

like image 233
Tom Avatar asked Jul 10 '18 23:07

Tom


People also ask

How do I merge two objects in TypeScript?

Use the spread syntax (...) to merge objects in TypeScript, e.g. const obj3 = { ... obj1, ... obj2 } . The type of the final object will successfully be inferred, so trying to add or remove properties from it will cause the type checker to show an error.

What are partials in TypeScript?

The Partial type in TypeScript is a utility type which does the opposite of Required. It sets all properties in a type to optional.

What is declaration merging?

For the purposes of this article, “declaration merging” means that the compiler merges two separate declarations declared with the same name into a single definition. This merged definition has the features of both of the original declarations.


Video Answer


2 Answers

The easiest way would be to remove the type annotations and let type inference do its work:

interface IComplete {
    a: string,
    b: string
}

const part1 = {
    a: 'hello'
}

const part2 = {
    b: 'world'
}

const together: IComplete = {
    ...part1,
    ...part2
}

This does have certain deleterious effects, e.g. parameters in function expressions in part1 or part2 won't get inferred types.

Instead you can use Pick - unfortunately you do have to write out the keys that you're picking from each type when using this approach:

interface IComplete {
    a: string,
    b: string,
    c: number
}

const part1: Pick<IComplete, "a"> = {
    a: 'hello'
}

const part2: Pick<IComplete, "b" | "c"> = {
    b: 'world',
    c: 43
}

const together: IComplete = {
    ...part1,
    ...part2
}
like image 155
Ryan Cavanaugh Avatar answered Oct 04 '22 05:10

Ryan Cavanaugh


Frankly, I do not fully understand why typescript transpiler does not permit that syntax.

But the below codes can bypass it and achieve what you want.

interface IComplete {
    a: string,
    b: string
}

const part1: Partial<IComplete> = {
    a: 'hello'
}

const part2: Partial<IComplete> = {
    b: 'world'
}

const together: IComplete = {
    ...part1,
    ...part2
} as IComplete
like image 22
Hyuck Kang Avatar answered Oct 04 '22 04:10

Hyuck Kang