DEV Community

suin
suin

Posted on

Introducing FillKeys Utility Type for Easier Destructuring of Discriminated Unions in TypeScript

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)