Reputation: 8137
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.
Upvotes: 16
Views: 12317
Reputation: 1789
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
Upvotes: 3
Reputation: 329953
@RyanCavanaugh's answer is good. One way to let type inference do its work while still getting the benefits of the compiler ensuring that part1
and part2
are Partial<IComplete>
is to use a helper function as below:
interface IComplete {
a: string,
b: string,
c: (x: string) => number,
d: (x: number) => string
}
// helper function
const asPartialIComplete = <T extends Partial<IComplete>>(t: T) => t;
const part1 = asPartialIComplete({
a: 'hello',
c: (x) => x.length
})
const part2 = asPartialIComplete({
b: 'world',
d: (x) => x + ""
});
const together: IComplete = {
...part1,
...part2
}
In the above, both part1
and part2
are constrained by asPartialIComplete
to be valid Partial<IComplete>
objects (so the parameters of the c
and d
methods are inferred as string
and number
respectively). But the types of part1
and part2
are narrow enough for the compiler to realize that typeof part1 & typeof part2
is IComplete
.
Hope that also helps. Good luck!
Upvotes: 9
Reputation: 221302
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
}
Upvotes: 7