In TypeScript development, discriminated unions are incredibly useful, but they can pose some problems when trying to destructure objects. In this article, I'll discuss the issues and the solution using the FillKeys utility type.
The Problem
As demonstrated in the following sample code, attempting to destructure a union type of Success
and Failure
results in a compilation error.
type Success = {
type: "success";
value: number;
};
type Failure = {
type: "failure";
error: Error;
};
declare const result: Success | Failure;
const { type, value, error } = result; // Cannot destructure
// Property 'value' does not exist on type 'Success | Failure'.(2339)
// Property 'error' does not exist on type 'Success | Failure'.(2339)
The Solution
We can solve the above problem using the FillKeys utility type. Modify the result
type to FillKeys<Success | Failure>
and perform the destructuring as follows.
type FillKeys<T> = (
(T extends T ? keyof T : never) extends infer AllKeys
? T extends T
? { [K in keyof T]: T[K] } & {
[K in AllKeys extends keyof T
? never
: AllKeys extends string
? AllKeys
: never]?: undefined;
}
: never
: never
) extends infer T
? { [K in keyof T]: T[K] }
: never;
declare const result: FillKeys<Success | Failure>;
const { type, value, error } = result; // Destructuring is now possible! No compilation errors
When using the FillKeys utility type to transform Success | Failure
, the resulting type becomes:
{
type: "success";
value: number;
error?: undefined;
} | {
type: "failure";
error: Error;
value?: undefined;
}
This type considers both Success
and Failure
types, and properties not present in each type are added as optional with undefined
. This prevents compilation errors when performing destructuring.
Specifically, the Success
type does not have an error
property, so error?: undefined;
is added. Similarly, the Failure
type does not have a value
property, so value?: undefined;
is added.
This makes destructuring discriminated unions more straightforward, and type narrowing still works.
declare const result: FillKeys<Success | Failure>;
const { type, value, error } = result;
if (type === "success") {
value * 100;
} else {
error.message;
}
Conclusion
When working with discriminated unions, you might encounter difficulties with destructuring. However, by using the FillKeys utility type, you can overcome these issues, making destructuring more manageable. As type narrowing also works, development efficiency is improved. The FillKeys utility type is a valuable tool for TypeScript developers using discriminated unions.