Hone logo
Hone
Problems

Advanced Union Type Helpers in TypeScript

TypeScript's union types are powerful for representing values that can be one of several types. However, working with them can sometimes be verbose and repetitive. This challenge asks you to implement utility types that simplify common operations on union types, making your code cleaner and more maintainable. These helpers will allow you to extract specific properties or filter union members based on certain criteria.

Problem Description

You are tasked with creating three TypeScript utility types: PickUnion, ExcludeUnion, and FilterUnion. These types will operate on union types and provide enhanced functionality.

  • PickUnion<T, K>: Given a union type T and a union of keys K, this type should extract the properties from T that are also present in K. Essentially, it's a union of the properties of T that are also in K.
  • ExcludeUnion<T, U>: Given a union type T and another union type U, this type should exclude all members of U from T. This is similar to the built-in Exclude utility type, but applied to union types.
  • FilterUnion<T, Condition>: Given a union type T and a conditional type Condition, this type should filter the members of T based on the Condition. The Condition should be a conditional type that evaluates to T or never for each member of T.

Key Requirements:

  • The utility types must be implemented using conditional types and mapped types.
  • The implementations should be type-safe and accurately reflect the intended behavior.
  • The code should be well-formatted and easy to understand.

Expected Behavior:

The utility types should correctly handle various union types and conditional types. They should also handle edge cases such as empty unions and invalid conditions gracefully (though error handling isn't explicitly required, the types should not produce errors).

Examples

Example 1: PickUnion

type T1 = { a: string; b: number; c: boolean } | { d: string; e: number } | { f: boolean };
type K1 = { a: string; e: number; f: boolean };

type Result1 = PickUnion<T1, K1>; // Expected: { a: string; e: number; f: boolean }

Explanation: Result1 should contain only the properties a, e, and f because they exist in both T1 and K1.

Example 2: ExcludeUnion

type T2 = string | number | boolean;
type U2 = number;

type Result2 = ExcludeUnion<T2, U2>; // Expected: string | boolean

Explanation: Result2 should exclude number from T2, leaving only string and boolean.

Example 3: FilterUnion

type T3 = { a: string } | { b: number } | { c: boolean };

type Result3 = FilterUnion<T3, T is { a: string } ? T : never>; // Expected: { a: string }

type Result4 = FilterUnion<T3, T extends { b: number } ? T : never>; // Expected: { b: number }

Explanation: Result3 filters T3 to only include objects that have the property a. Result4 filters T3 to only include objects that have the property b.

Constraints

  • The utility types must be implemented using TypeScript's type system features (conditional types, mapped types, etc.). No runtime code is allowed.
  • The code should be compatible with TypeScript 4.x or higher.
  • The solutions should be reasonably efficient in terms of type inference. While performance isn't a primary concern, excessively complex or inefficient type definitions should be avoided.
  • The utility types should be generic and work with any valid union types.

Notes

  • Consider how to handle cases where the union type is empty.
  • Think about the order of operations when combining these utility types.
  • The FilterUnion type requires a good understanding of conditional types and how they can be used to filter union members. The Condition type is crucial for defining the filtering logic.
  • This challenge is designed to test your understanding of advanced TypeScript type manipulation techniques. Don't be afraid to experiment and explore different approaches.
Loading editor...
typescript